面试遇到Runtime的第二天-isa和meta-Class
2020/6/23 23:26:55
本文主要是介绍面试遇到Runtime的第二天-isa和meta-Class,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
本文主要写一下,runtime中关于类,元类的结构和他们之间的关系。其实应该在上一篇文章面试遇到Runtime的第一天中先写本文的内容,但是写那天刚好在整理category的知识点,所以趁热打铁的就写在了上一篇文章。如果在阅读时遇到有比较难理解的点,不妨可以先阅读本文,再去阅读面试遇到Runtime的第一天中的内容。
runtime简介
阅读过面试遇到Runtime的第一天,你肯定就已经知道,runtime通俗点说就是一套底层API,那么我们通过什么方式可以调用到它呢?
- OC(Objective-C 后文简称OC)上层方法直接调用
我们编写的OC代码,在编译阶段会自动转换成运行时代码
- NSObject API
NSObject可以看做是所有类的基类(NSProxy除外),NSObject协议中定义了如下这些可以从runtime中获取信息的方法
- (BOOL)isKindOfClass:(Class)aClass; - (BOOL)isMemberOfClass:(Class)aClass; - (BOOL)conformsToProtocol:(Protocol *)aProtocol; - (BOOL)respondsToSelector:(SEL)aSelector; 复制代码
- runtime api
当然我们也可以手动调用runtime API,需要导入objc/Runtime.h和objc/message.h两个头文件
NSObject
从NSObject源码入手
先看NSObject的定义
@interface NSObject <NSObject> { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-interface-ivars" Class isa OBJC_ISA_AVAILABILITY; #pragma clang diagnostic pop } 复制代码
只有一个Class类型的isa,找到Class的定义,是一个objc_class的结构体
typedef struct objc_class *Class; 复制代码
这里我们直接看Objc2.0之后,objc_class的定义(忽略了部分本文不讨论的代码)
typedef struct objc_class *Class; typedef struct objc_object *id; @interface Object { Class isa; } @interface NSObject <NSObject> { Class isa OBJC_ISA_AVAILABILITY; } struct objc_object { private: isa_t isa; } struct objc_class : objc_object { // Class ISA; Class superclass; cache_t cache; // formerly cache pointer and vtable class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags } union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; } 复制代码
从源码中我们可以证实以下几点:
- objc_object被typedef为id,也就是我们常见的id类型,id 这个struct的定义本身就带了一个 * 所以我们在使用其他NSObject类型的实例时需要在前面加上\ *, 而使用 id 时却不用。
- objc_class继承自objc_object,所以OC中的类也是一个对象,而类对象的isa则指向meta class
- object类和NSObject类里面分别都包含一个objc_class类型的isa
对象,类,元类
这张图中的关系我们需要好好的理解(并且记忆)一下,也有助于我们之后对runtime的理解
- Root class (class)其实就是NSObject,NSObject是没有超类的,所以Root class(class)的superclass指向nil。
- 每个Class都有一个isa指针指向唯一的Meta class
- Root class(meta)的superclass指向Root class(class),也就是NSObject,形成一个回路。
- 每个Meta class的isa指针都指向Root class (meta)。
元类
元类这个概念比较抽象,他为什么存在呢?可以从调用类方法开始说起:
NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding]; 复制代码
上面这句代码可以正确执行,说明OC不仅可以给对象发送消息,也同样可以给类发送消息,因为上面我们也提到了,OC中类也是一个对象。
那么,当给类发送消息的时候,类对象的isa就指向了meta-class,所以要去meta-class中去找到该方法的实现。
可以理解为,meta-class的存在是为了统一OC中所有对象消息查找转发的流程,或者说是引入meta-class来保证无论是类还是对象都能通过相同的机制查找方法的实现。
- 实例方法调用时,通过对象的 isa 在类中获取方法的实现
- 类方法调用时,通过类的 isa 在元类中获取方法的实现
这里又引出了几个面试题:
问:对象的实例方法存在哪儿?类方法存在哪儿?
答:当一个对象的实例方法被调用时,会通过isa找到对应的类,然后在该类的class_data_bits_t中去查找方法对应的实现,所以实例方法是存在类中的,而类方法是存在元类中的。
问:类方法在元类中是以什么形式存在?
答:类方法在元类中是以实例方法存在,并且,对象在类中是一个实例,类在元类中也是一个实例。所以,类的类方法,就是元类的实例方法。(这里比较绕,多读几遍,好好理解)
cache_t的具体实现
继续看源码
struct cache_t { struct bucket_t *_buckets; mask_t _mask; mask_t _occupied; } typedef unsigned int uint32_t; typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits typedef unsigned long uintptr_t; typedef uintptr_t cache_key_t; struct bucket_t { private: cache_key_t _key; IMP _imp; } 复制代码
Cache的作用主要是为了加速消息分发, 系统会对方法和对应的地址进行缓存,所以在实际运行中,大部分常用的方法都是会被缓存起来的,Runtime系统实际上非常快,接近直接执行内存地址的程序速度。
class_data_bits_t的具体实现
struct class_data_bits_t { // Values are the FAST_ flags above. uintptr_t bits; } struct class_rw_t { uint32_t flags; uint32_t version; const class_ro_t *ro; method_array_t methods; property_array_t properties; protocol_array_t protocols; Class firstSubclass; Class nextSiblingClass; char *demangledName; } struct class_ro_t { uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; #ifdef __LP64__ uint32_t reserved; #endif const uint8_t * ivarLayout; const char * name; method_list_t * baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; const uint8_t * weakIvarLayout; property_list_t *baseProperties; method_list_t *baseMethods() const { return baseMethodList; } }; 复制代码
这两部分本文不做过多的阐述,后面在写消息查找转发的时候在详细介绍,这里先简单了解一下即可。
实战
上面啰里啰嗦的写了一堆,看文章看到这里可能也是似懂非懂的样子,下面通过我们最常用的几个方法,来实战一下上面讲解的理论知识,帮助我们理解记忆。
[self class] 与 [super class]
如下代码打印结果是什么?
@interface Sark : NSObject 复制代码
- (id)init { self = [super init]; if (self) { NSLog(@"%@", NSStringFromClass([self class])); NSLog(@"%@", NSStringFromClass([super class])); } return self; } 复制代码
打印结果:
14:17:56.444058+0800 test[30316:787077] Sark 14:17:56.444177+0800 test[30316:787077] Sark 复制代码
解释一下为什么NSLog(@"%@", NSStringFromClass([super class]));也输出了Sark
- self 不一定是当前类, self只是一个形参 objc_msgSend(id self,SEL _cmd) 取决于消息的接收者
- 在调用[super class]的时候,runtime会去调用objc_msgSendSuper方法
OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ ) /// Specifies the superclass of an instance. struct objc_super { /// Specifies an instance of a class. __unsafe_unretained id receiver; /// Specifies the particular superclass of the instance to message. #if !defined(__cplusplus) && !__OBJC2__ /* For compatibility with old objc-runtime.h header */ __unsafe_unretained Class class; #else __unsafe_unretained Class super_class; #endif /* super_class is the first class to search */ }; 复制代码
在objc_msgSendSuper方法中,第一个参数是一个objc_super的结构体,这个结构体里面有两个变量,一个是接收消息的receiver,一个是当前类的父类super_class。
- objc_msgSendSuper的工作原理应该是这样的: 从objc_super结构体指向的superClass父类的方法列表开始查找selector,找到后以objc->receiver去调用父类的这个selector。注意,最后的调用者是objc->receiver = self,而不是super_class!
isKindOfClass 与 isMemberOfClass
如下代码打印结果是什么?
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]]; BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]]; NSLog(@"%d %d %d %d", res1, res2, res3, res4);// yes no no no 复制代码
查看isKindOfClass和isMemberOfClass的源码:
+ (Class)class { return self; } - (Class)class { return object_getClass(self); } Class object_getClass(id obj) { if (obj) return obj->getIsa(); else return Nil; } inline Class objc_object::getIsa() { if (isTaggedPointer()) { uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK; return objc_tag_classes[slot]; } return ISA(); } inline Class objc_object::ISA() { assert(!isTaggedPointer()); return (Class)(isa.bits & ISA_MASK); } + (BOOL)isKindOfClass:(Class)cls { for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {// 第一次就获取到了class -> meta class 然后再遍历superclass if (tcls == cls) return YES; } return NO; } - (BOOL)isKindOfClass:(Class)cls { for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {//第一次获取了self -> class 然后再遍历superclass if (tcls == cls) return YES; } return NO; } + (BOOL)isMemberOfClass:(Class)cls { return object_getClass((id)self) == cls; } - (BOOL)isMemberOfClass:(Class)cls { return [self class] == cls; } 复制代码
分析:
- isKindOfClass 区分实例方法和类方法的实现不同 类方法循环首先取了isa指针 第一次就获取到了class -> meta class 然后再遍历superclass 而实例方法是第一次获取了self -> class 然后再遍历superclass
- isMemberOfClass 没有遍历 直接比较
因此,第一行代码 BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];的判断流程是:
- [NSObject class] --> NSObject
- NSObject 和 NSObject meta-Class 不相等 然后对NSObject meta-Class取super class
- NSObject 和 NSObject 相等 返回 yes
对NSObject meta-Class取super class用到里图里红圈标识出来的关系,得到结果是NSObject
第二行代码 BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];的分析流程是:
- [NSObject class] --> NSObject
- object_getClass(NSObject) --> NSObject meta-Class
- NSObject 和 NSObject meta-Class不相等 返回 NO
第三行代码 BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]]; Sark取meta-Class然后再一直遍历找super_class,最终也不会找到相等的,返回NO
第四行代码 BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]]; Sark和Sark meta-Class不相等,返回NO
总结
经过上面两个小问题的实战,是不是对对象、类、元类之间的关系有了更深刻的理解,简单总结一下:
- 每个类都有一个唯一对应的元类
- 类对象的isa指向了元类
- 元类中以实例方法的形式保存了类的类方法
- 根元类(Root meta class)的isa指向自己,super class为NSObject ,形成一个闭环
这篇关于面试遇到Runtime的第二天-isa和meta-Class的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值