数据结构
1. objc_object
OC中平时使用的所有的对象(id类型),在Runtime中对应为objc_object的结构体。
objc_object结构体:
- isa_t
- 关于isa操作相关
- 弱引用相关
- 关联对象相关
- 内存管理相关
2. objc_class
OC中所使用的类(Class),在Runtime中对应为objc_class。继承自objc_object。 Class也是一个对象,称之为类对象。
objc_class结构体:
Class superClass;指向父类的指针cache_t cache;方法缓存,进行消息传递过程中会使用到class_data_bits_t bits;类所定义的协议、属性以及方法
3. isa指针
- 指针型isa:isa的值代表Class的地址。
- 非指针型isa:isa的值得部分代表Class的地址。
在使用指针寻址过程中,例如在64位设备上,也许30、40位就能保证寻找到所有Class的地址,那么非指针型isa可以存储一些其他内容以达到节省内存的目的。
isa指向
- 关于对象,其指向类对象。
- 关于类对象,其指向元类对象。
- 所有元类对象的isa指针都指向根元类对象,包括根元类对象自己。
graph LR
A[实例] -->|isa| B[Class]
B -->|isa| C[MetaClass]
4. cache_t
对象调用方法都是通过编译器进行方法查找。而编译器会将经常查找的方法进行缓存,下次查找的时候先进入缓存中查找,这样会大大节省时间,从而提高查找速度,cache_t就是为此而生的。
- 用于快速查找方法执行函数
- 是可增量扩展的哈希表结构
- 是局部性原理的最佳应用
可以理解为是装满了bucket_t结构体的Hash表。bucket_t包含两个主要的成员变量:key、IMP。其中的Key对应OC中的SEL,就是一个字符串,表示方法的名字;IMP则是指向方法实现首地址的指针。
5. class_data_bits_t
- class_data_bits_t主要是对class_rw_t的封装。
- class_rw_t代表了类相关的读写信息,是对class_ro_t的封装。
- class_ro_t代表了类相关的只读信息。
class_rw_t/class_ro_t中的rw/ro代表readwrite/readonly
6. class_rw_t
- class_ro_t
- properties:属性
- protocols:协议
- methods:方法列表(一般为分类添加的方法)
class_rw_t结构体中的properties、protocols、methods都是继承自list_array_tt的二维数组。
7. class_ro_t
- name:类名
- ivars:成员变量
- properties:属性
- protocols:协议
- methodList:方法列表(原始定义的方法)
class_ro_t结构体中的ivars、properties、protocols、methodList都是一维数组。
8. method_t
SEL name;函数名称const char* types;返回值和参数(Type Encodings)IMP imp;指向函数实现的首地址指针
函数四要素:名称、返回值、参数、函数体。
消息传递
1. 对象、类对象、元类对象
实例对象是objc_object数据结构; 类对象和元类对象,都是属于objc_class数据结构,而objc_class数据结构又是继承自objc_object数据结构。 实例对象可以通过isa指针找到它的类对象,类对象存储实例方法列表等信息; 类对象可以通过isa指针找到它的元类对象,元类对象存储类方法列表等信息; 根类对象的superClass指针指向nil; 所有元类对象的isa指针,都指向根元类对象,包括根元类自己; 根元类对象的superClass指针指向根类对象;
2. 实例方法的消息传递
- 当我们调用实例方法时,系统会先根据当前实例的isa指针找到它的类对象;
- 在类对象中先查找缓存,再遍历方法列表查找同名的方法实现;
- 如果没有查找到,则会按照类对象的superClass指向,依次遍历父类的方法列表,直到根类对象;
- 如果一直到根类对象也没有查找到该方法,则进入消息转发流程。
3. 类方法的消息传递
- 当我们调用类方法时,系统会通过类对象的isa指针找到它的元类对象;
- 在元类对象中先查找缓存,再遍历方法列表查找同名的方法实现;
- 如果没有查找到,则会按照元类对象的superClass指向,依次遍历父元类的方法列表,直到根元类对象;
- 如果一直到根元类对象也没有查找到该方法,因为根元类的superClass指向根类对象,则会在根类对象中找同名的实例方法实现;
- 如果根类对象中有同名的实例方法,则会执行该实例方法;
- 如果根类对象中没有同名的实例方法,则进入消息转发流程。
4. [self class]、[super class]
- [self class] ==> objc_msgSend(self, @selector(class))
- [super class] ==> objc_msgSendSuper(super, @selector(class))
虽然在调用super的方法时,runtime中传递的参数是super,但这里的super其实是一个objc_super类型的结构体,该结构体中包含了一个receiver对象,指的是当前对象,而不是其父类对象。所以[self class]和[super class]的返回都是当前对象的class,区别是super调用时,跳过了在当前类对象的方法列表遍历@selector(class)的过程,直接从父类的方法列表开始查。
5. 缓存查找cache_t
根据给定的方法选择器(SEL),通过哈希查找,来映射出要找的bucket_t在数组中的位置,最后取得该方法的实现(IMP)。
哈希查找:通过给定的Key,经过哈希函数的算法算出一个值,算出的值就是这个Key在数组中对应元素的索引位置。
6. 当前类中查找
- 对于已排序好的列表,采用二分查找算法查找;
- 对于没有排序的列表,采用一般遍历方法查找。
7. 消息转发流程
-
resolveInstanceMethod:/resolveClassMethod:首先系统会回调该类方法,Instance表示实例方法的消息转发、Class表示类方法的消息转发。参数是方法选择器(SEL),返回值是BOOL类型,表示系统要不要解决该方法实现。 -
forwardingTargetForSeLector:当第1步的方法返回NO时,系统会回调该方法。参数也是方法选择器(SEL),返回值是id,表示这条消息应该由哪个对象来处理。 -
methodSignatureForSelector:若第2步的方法没有指定转发目标,系统会调用该方法,也是消息转发的最后一次机会。参数是方法选择器(SEL),返回值是methodSignature类型的对象,封装了方法选择器返回值的类型以及参数个数和参数类型。此时若返回了方法签名,系统会调用forwardInvocation:方法;若forwardInvocation:无法处理这条消息,或methodSignatureForSelector:返回了nil,会导致Crash。
graph LR
A[resolveInstanceMethod:] -->|返回NO| B[forwardingTargetForSeLector:]
A -->|返回YES| F[消息已处理]
B -->|返回nil| C[methodSignatureForSelector:]
B -->|返回转发目标| F
C -->|返回方法签名| D[forwardInvocation:]
C -->|返回nil| E[消息无法处理]
D --> E
D --> F
8. 动态方法解析
- 动态运行时语言将函数决议推迟到运行时。
- 编译时语言在编译期进行函数决议。
当我们把一个属性标识为@dynamic时,代表着不需要编译器在编译时为我们生成该属性get/set方法的具体实现,而是在运行时我们具体的调用了该属性的get/set方法时再去添加具体的实现。
9. 能否向编译后的类中增加实例变量?
不能。类在编译之前,已经完成了实例变量的布局,存放于class_ro_t,是只读的。
10. 能否向动态添加的类中增加实例变量?
可以。在动态添加类的过程中,只要在注册之前,就可以添加实例变量。