RunTime
数据结构
- objc_object
- objc_class
- isa指针
- method_t
objc_object id == objc_object
- isa_t
- 关于isa操作相关
- 弱引用相关
- 关联对象相关
- 内存管理相关
objc_class
- 对应OC中的类(Class),class也是一个对象。类对象
- 继承自objc_object
- 包含superClass指针,指向一个class
cache_t cache(成员变量),一个方法缓存的结构
① 快速查找方法执行函数
② 可增量扩展的哈希表结构
③ 局部性原理的最佳应用
局部性原理:我们将调用频次较高的几种方法,放到一块开辟的空间中,这样我们在下次调用时就能更加快速的调用方法
构成:由一个数组来实现的
数组中由多个bucket_t元素构成,bucket_t 包含:IMP指针和key
class_data_bits_t bits
class_data_bits_t主要是对class_rw_t的封装
class_rw_t代表了类相关的读写信息、对class_ro_t的封装
class_data_bits_t bits 组成
-
class_rw_t
① class_ro_t 代表了类相关的只读信息
② protocols
③ properties
④ methods
class_rw_t.png -
class_ro_t
Class_ro_t.png - isa指针
① 指针型isa 的值代表Class的地址
② 非指针型isa 的值的部分代表Class的地址
③ 产生这两种的初衷:我们在寻址实际上30~40位数就可以保证我们寻找到所有class的地址,多出来的位可以存储一些别的内容,来达到节省内存的目的
④ 指向问题:- 关于对象,其指向类对象
- 关于类对象,其指向元类对象
-
如果是实例方法从类对象查找,如果是类方法从元类对象查找
isa指针.png
method_t
method_t.png- SEL name:方法的名称
- const char *types:函数返回值和参数的组合
- IMP imp:无类型的函数指针
type Encodings技术
Type Encoding.png- 使method指向了函数体的返回值,参数等
-
返回值占据第一位,返回值只有一个或者没有
20210622-155020.png
数据结构总结
Runtime数据结构.png对象、类对象、元类对象
- 类对象存储实例方法列表等信息
- 元类对象存储类方法类表等信息
- 类对象和元类对象都是objc_class数据结构,由于继承了objc_object数据结构所以才有isa指针,
- 所以才能实例对象找到类对象访问它的实例方法列表等信息,
- 类对象通过isa可以找到元类对象访问类方法列表等信息
- 元类对象的isa指针(包含根元类对象)都指向根元类对象
- 根元类对象的superclass指向的根类对象
注:当在根类中的类方法没找到,会指向根类对象中去查找它的同名的实例方法
消息传递过程
实例方法消息传递过程
1、 调用了一个实例对象A对象的实例方法
2、 通过A实例对象的isa指针找到它的类对象,类对象中遍历方法列表去查找同名的方法实现,找到后Runtime发送消息调用
3、 如果没有找到就会根据superclass指针指向的父类对象,并查找其方法列表。。。直到根类对象中的方法列表,如果找到Runtime发送消息调用,如果到根类对象还没有找到就会返回nil
类方的消息传递过程
1、 调用一个类对象的类方法。
2、 通过类对象的isa指针找到元类对象,元类对象中遍历方法列表查找同名的类方法实现,找到后直接调用
3、如果没有找到就会通过superclass指针指向父元类对象,并查找其类方法列表。。。直到根元类对象中的类方法列表,如果找到直接调用,如果没有找到进入到消息转发
4、根元类对象如果没有找到会superclass会指向根类对象,并查找同名的实例方法,如果找到直接调用,如果没有找到返回nil
消息传递
消息传递.png消息传递Supr.png
消息传递流程.png
1、缓存查找
例: 给定值是SEL 目标值是对应bucket_t的IMP。
- 首先给定的函数选择器通过一个函数来映射出bucket_t在数组当中位置,这个过程是hash查找
- 哈希查找实际上,通过给定的一个值比如说方法的选择器经过哈希函数的算法酸楚的值实际上就是给定值在对应数组当中所对应的索引位置。
① f(key) = key & mask
② 通过哈希查 解决查找效率的问题
③ 通过哈希查找,找到对应的bucket_t,就可以拿到IMP
2、当前类中查找
对于已排序好的列表,采用二分查找算法查找方法对应执行函数
对于没有排序的列表,采用一般遍历查找方法对应执行函数
3、父类逐级查找
父类逐级查找的过程.png消息转发
消息转发.pngMethod-Swizzling
MethodSwizzling.pngMethod method1 = class_getInstanceMethod(self, @selector(test));
Method method2 = class_getInstanceMethod(self, @selector(Mytest));
method_exchangeImplementations(method1, method2);
使用场景
替换系统的viewdidLoad或者viewwillDismiss中,在替换的方法中,添加我们自己的代码,例如添加时长统计,来统计进入当前界面的时长
动态添加方法
编译的时候没有这个方法,运行时有这个方法需要调用performSelector方法,在消息转发中的第一次处理函数resolveInstanceMethod动态添加DoThings:Num:方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@",NSStringFromSelector(sel));
if(sel == @selector(DoThings:Num:)){
class_addMethod([self class], sel, (IMP)MyMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void MyMethodIMP(id self,SEL _cmd,NSString * name,NSInteger num){
NSLog(@"调用了消息转发的内容");
NSLog(@"%@",NSStringFromSelector(_cmd));
NSLog(@"%@",name);
NSLog(@"%ld",(long)num);
}
动态添加调用的函数
class_addMethod([self class], sel, (IMP)MyMethodIMP, "v@:");
动态添加函数参数说明
20210622-161516.png动态方法解析
@dynamic
-动态运行时语言将函数决议推迟到运行时。
① 当我们把一个属性标示为@dynamic的时候代表着不需要编译器在编译时,为我们生成属性的setter跟getter方法的具体实现,而在在具体运行时我们调用了setter、geter方法的时候,再去添加具体的实现。
② 这种功能也只有动态运行时特性的语言支持
- 编译时语言在编译期进行函数决议
MST
nsobject有父类吗?
没有nsobject是根类
当在跟类中的类方法没找到,但是有同名的实例方法实现,那么会不会崩溃?会不会产生实际的调用?
由于根元类对象的superclass指针指向了根类对象,去查找实例同名的实例方法,如果找到就会执行同名的实例方法调用
下图中打印的是什么?
笔试题1.png[self class]会转化成
objc_msgSend(<#id _Nullable self#>, <#SEL _Nonnull op, ...#>)
phone的实例对象,[self class],通过isa找到phone类对象,phone类对象是没有class的,通过superclass找到父类的。。。直到查找到根类nsobject,有calss的实现,打印出来的自然是phone
[super class]会转化成
objc_msgSendSuper(<#struct objc_super * _Nonnull super#>, <#SEL _Nonnull op, ...#>)
objc_msgSendSuper实际的接收者仍然是phone的这个实例对象,objc_msgSendSuper传递的含义,从phone的这个实例对象的父类开始查找, 直到查找到根类nsobject有class的实现,所以打印出来的结果还是phone
打印结果都是phone
对页面的进出添加进出信息
使用MethodSwizzling来交换viewDidLoad和viewWillAppear,在替换代码中添加一些埋点操作
[obj foo]和objc_msgSend()函数之间有什么关系?
- 向 obj对象发送一条foo的消息,[obj foo]在编译期处理以后就变成了objc_msgSend(obj,@selector(foo))
- 由于没有参数,所以 objc_msgSend函数调用只有2个参数
- 然后就开始了runtime的消息传递过程
runtime如何通过Selector找到对应的IMP地址的?
- 查找当前实例所对应类对象的缓存,是否有selelctor对应imp实现如果缓存命中了,我们就把命中的缓存函数返回给调用方。
- 如果缓存没有就去当前类的方法列表查找selector对应的imp实现
- 当前类如果没有命中,就根据当前类的superclass逐级查找父类的方法列表。。。直到根类对象,直到查到selector方法的imp实现
能否向编译后的类中增加实例变量?
class_ro_t ro代表的是readonly
编译后的类是无法添加实例变量的。
能否向动态添加的类中增加实例变量?
可以的,动态添加的这个类过程当中只要在它调用注册方法之前去完成实例变量的添加就是可以实现的
isa指针的含义?
- 非指针类型 isa值的部分代表calss地址
- 指针类型isa 代表class地址