4.动态运行时(RunTime)
- 基础数据结构
- 对象、类对象、元类对象是怎么理解的?以及他们之间的关系是怎样的(实例与类对象之间的关系以及类对象与元类对象之间的关系)?
- OC语言中的消息传递机制是怎样的?
- 方法缓存 (我们进行方法查找的过程当中,如何进行缓存的方法查找?会使用到系统或者说Runtime中一个方法缓存的机制,这个机制又是怎样运行的呢?)
- 消息转发流程是怎样的?
- Method-Swizzling(方法混写)是Runtime的一种运用(我们可以再运行时,去替换一些方法的实现,也得益于动态运行时这一个特性),可能会结合实际的场景,比如说通过时长统计框架这样的考察来了解对于Runtime的Methood-Swizzling技术的一个实际的运用
- 动态添加方法(Runtime提供的功能实现)
- 动态方法解析(Runtime提供的功能实现)
对象、类对象、元类对象(类对象、元类对象分别是什么,他们的区别是什么?)
- 类对象是存储实例方法列表等信息的结构,实例对象可以通过isa指针找到自己的类对象,来访问实例方法
- 元类对象存储类方法列表等信息的内容, 类对象可以通过isa找到自己的元类对象,来访问类方法
红色箭头代表superClass指向,黑色虚线表示isa指针的指向
- Root class代表根类,父类指向nil,可以理解为NSObject
对应这个类对象,有子类Superclass(class),以及子类的子类Subclass(class) - 当我们给定一个实例的时候,左边的实例(instance of Subclass)是id类型的,也就是objc_object数据结构,内含isa成员变量指针,指向这个实例所对应的类对象 (例如有个类叫ClassA,创建出一个instanceA,它的isa指针就指向classA)
所以:实例和类对象的关系:实例(instance)通过isa指针可以找到自己的类对象(class) - 中间那行类对象是objc_class结构, 最右侧(Superclass)分别为子类,父类和根类的元类对象(元类对象之间也是继承关系)
- 类对象(class)也有isa指针,指向元类对象(Superclass meta)
- 元类对象因为也是objc_class类型,所以也有isa指针。对于任何一个元类对象,包括根元类对象自身,它的isa指针都指向根元类对象
- 根元类对象的superClass指针指向它的根类对象
类对象和元类对象的区别和联系:
- 实例(instance)通过isa指针可以找到自己的类对象(class),类对象当中存储方法列表等信息
- 以及类对象可以通过isa指针找到它的元类对象(Superclass meta),从而可以访问一些关于类方法列表的相关信息。
- 类对象和元类对象都是objc_class数据结构的,因为objc_class继承了objc_object,所以它们才有isa指针,进而才能实现我们刚才所描述的操作。
如果我们调用了一个类方法,会从元类的方法列表中查找,查找的过程是沿着红色箭头指向逐级查找
当我们调用的类方法,在元类当中找不到的时候,就会找根类对象当中同名的实例方法实现
所以,如果我们调用一个类方法,没有对应的实现,但是有同名的实例方法实现,会不会发生崩溃?会不会产生实际的调用?
- 会产生实际调用,原因是根元类对象的superClass指针指向了根类对象
- 所以在元类中查找不到类方法的时候,就会顺着superClass指针去实例方法列表中查找
- 若有同名方法,就会实现调用
简单的描述下消息传递的过程:
- 假如我们调用了实例方法A(instance),系统会根据当前实例的isa指针,找到它的类对象。
在它的类对象中遍历方法列表,若没找到
会顺着SubClass指针指向去父类(SuperClass)类对象方法列表当中查找
一直到根类(RootClass)对象的方法列表查找,如果没找到就会进入消息转发流程 - 如果调用的是类方法(SubClass),系统会通过类对象的isa指针去原来对象中查找,顺次遍历方法列表
直到根元类(Rootclass meta)对象
然后再到根类对象(RootClass),都没有的话,进入消息转发流程 - 遍历类方法和实例方法的区别是:在根元类的指向根元类的super会指向根类对象
OC语言中的消息传递机制是怎样的?
- objc_msgSend函数固定要接受两个参数,id类型的self和SEL类型的方法选择器名称
再后面才是消息传递的真正的方法参数。 - 对于任何一个消息传递[self Class],通过编译器都会转换成关于objc_msgSend的函数调用
第一个参数是消息传递的接收者self ,第二个参数是我们传递的消息名称,也叫做选择器 - 对于消息传递,在编译器层面,其实转换成了函数调用
[self Class] ---- objc_msgSend(self,@selector(class))
另一个函数:
- 也有两个固定参数, super结构体类型的指针和方法选择器(SEL op),后续是实际参数(...)
- super结构体中包含了receiver成员变量,这个接收者就是当前对象
因为super是编译器关键字,经过编译器编译之后,会解析成objc_super结构体指针
而结构体中的成员变量receiver,就是当前对象 - 不论是调用self class还是super class ,这条消息的接收者都是当前对象
那么self class和super class有什么区别呢?
- 在调用一个方法的时候,先会查找缓存,看缓存中是否有对应选择器名称的方法。
- 如果有,就通过函数指针调用函数,就完成了消息传递。
- 如果缓存没有,就会根据当前实例的isa指针,去查找当前类对象的方法列表
如果找到同样名称的方法,就通过函数指针调用这个函数实现,结束这次消息传递
如果当前类方法中没有,通过当前类对象的superClass指针去就逐级父类方法列表查找。
若父类也没有,就根据父类的superClass指针往上查找,直到nil,若都没有,就进入消息转发流程
总结:首先查缓存,没命中查当前类的方法列表,没命中顺次查找它的各个父类方法列表,都没命中转到消息转发流程。下个章节方法缓存再做补充
具体缓存查找流程
根据给定的方法选择器(SEL),来查找(bucket_t)中对应的方法实现(IMP)
- 缓存是否命中,缓存查找是哈希查找
- 当前类方法列表是否命中,已排序好的是二分查找,未排序好的是一般查找
- 逐级父类方法列表是否命中,根据superClass指针逐级查找父类,在父类中也是先查找缓存,再查找父类
看下这个笔试题:
id objc_msgSend(id self, SEL op, ...);
/*
返回值是一个id,objc_msgSend发送消息的本质就是,
第一个参数self就是当前响应消息的对象指针,记录这个对象的地址
SEL类型 类似C语言中的函数指针,记录方法列表。在OC里面就是方法名。
*/
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cz_l7gn4kjj52x1y4xtvjjvy_l40000gn_T_Student_28e7c0_mi_0,
NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
- 对于[self class]来说,首先会被转化成objc_msgSend函数调用
[super class]会被转化成objc_msgSendSuper函数调用,objc_msgSendSuper的参数虽然是super,
但super结构体里面包的receiver是当前对象,所以这两个方法的接收者都是当前对象 - 假如现在Phone,实例在8,初始化的时候,我们通过[self class]打印类信息
会通过8的isa指针找到Phone的类对象14在这里寻找class方法,本身是没有的
然后会通过superClass指针向上找,找到6号Mobile父类,他这里也没有class实现
一直找到根类对象5号,也就是NSObject,有class实现,就会调用class具体实现
返回给调用方,打印出来的就是Phone
id objc_msgSendSuper(struct objc_super *super, SEL op, ...);
/*
struct objc_super *super 结构体指针类型。-->struct objc_super{id receiver;Class super_class;} receiver:记录消息的接收者 super_class:表示super表示的父类是什么。super是调用父类的方法的。
*/
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cz_l7gn4kjj52x1y4xtvjjvy_l40000gn_T_Student_28e7c0_mi_1,
NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super)
{(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("class"))));
- 对于[super Class],实际的接收者仍然是Phone这个实例,调用了objc_msgSendSuper
也就是从父类的方法列表6开始查找,跨越了当前Phone的类方法列表14号,
但6号也没有Class方法,一直找到根类NSObject有Class,
接收者仍然是当前对象(id)self,返回的仍然是Phone - 所以此面试题打印结果两个都是Phone。