阅读笔记
神经病院 Objective-C Runtime 住院第二天——消息发送与转发
神经病院 Objective-C Runtime 出院第三天——如何正确使用 Runtime
objc_msgSend
[receiver message]会被编译器转化为:1
2
3
4
5// self: A pointer that points to the instance of the class that is to receive the message.
// op: The selector of the method that handles the message.
id objc_msgSend ( id self, SEL op, ... );
typedef struct objc_selector *SEL;
objc_selector 是一个映射到方法的C字符串。
在receiver拿到对应的selector之后,如果自己无法执行这个方法,那么该条消息要被转发。或者临时动态的添加方法实现。如果转发到最后依旧没法处理,程序就会崩溃。
编译期仅仅是确定了要发送消息,而消息如何处理是要运行期需要解决的事情。
objc_msgSend会做一下几件事情:
1.检测这个 selector 是不是要忽略的。
2.检查target是不是为nil。
如果这里有相应的nil的处理函数,就跳转到相应的函数中。
如果没有处理nil的函数,就自动清理现场并返回。这一点就是为何在OC中给nil发送消息不会崩溃的原因。
3.确定不是给nil发消息之后,在该class的缓存中查找方法对应的IMP实现。
如果找到,就跳转进去执行。
如果没有找到,就在方法分发表里面继续查找,一直找到NSObject为止。
4.如果还没有找到,那就需要开始消息转发阶段了。至此,发送消息Messaging阶段完成。这一阶段主要完成的是通过select()快速查找IMP的过程。

消息发送Messaging阶段lookUpImpOrForward函数逻辑
- 尝试从类的
cache中获取IMP - 如果在
cache缓存中获取失败,则再去类方法列表里面进行查找 - 如果以上尝试都失败了,接下来就会循环尝试父类的缓存和方法列表。一直找到
NSObject为止。 - 如果在父类中找到了该方法
method的IMP,接下来就应该把这个方法cache回自己的缓存中。 - 如果没有在父类的
cache中找到IMP,继续在父类的方法列表里面查找。 - 如果父类还没有找到,那么就会开始尝试
_class_resolveMethod方法。 - 回到
lookUpImpOrForward方法中,如果也没有找到IMP的实现,那么method resolver也没用了,只能进入消息转发阶段。
消息转发Message Forwarding阶段
- 到了转发阶段,会调用
id _objc_msgForward(id self, SEL _cmd,...)方法 - 在执行
_objc_msgForward之后会调用__objc_forward_handler函数。 - 这一步是替消息找备援接收者,如果这一步返回的是
nil,那么补救措施就完全的失效了,Runtime系统会向对象发送methodSignatureForSelector:消息,
消息发送和转发的全过程:
Category
OC在初始化的时候,会去加载map_images,map_images最终会调用objc-runtime-new.mm里面的_read_images方法。_read_images方法里面会去初始化内存中的map, 这个时候将会load所有的类,协议,Category。NSOBject的+load方法就是这个时候调用的。
加载完所有的category之后,就开始处理这些类别。
处理完之后的结果
1)、把category的实例方法、协议以及属性添加到类上
2)、把category的类方法和协议添加到类的metaclass上
先去调用addUnattachedCategoryForClass函数,申请内存,分配空间。remethodizeClass这个方法里面会调用attachCategories方法。remethodizeClass方法里面会用头插法,把新加的方法从头插入方法链表中。我们可以在Category里面覆盖原有的方法,因为头插法,新的方法在链表的前面,会优先被遍历到。
优点
实现多继承Multiple Inheritance
在OC程序中可以借用消息转发机制来实现多继承的功能。
通过forwardingTargetForSelector:方法,一个类可以做到继承多个类的效果,只需要在这一步将消息转发给正确的类对象就可以模拟多继承的效果。利用转发消息来实现的是“假”继承,respondsToSelector:和 isKindOfClass:不会考虑转发链。
Method Swizzling
Method Swizzling理论上可以在运行时通过类名/方法名hook到任何 OC 方法,替换任何类的实现以及新增任意类。Method Swizzling本质上就是对IMP和SEL进行交换。
Swizzling应该总在+load中执行。+load会在类初始加载时调用,+initialize方法是以懒加载的方式被调用的。Swizzling应该总是在dispatch_once中执行Swizzling在+load中执行时,不要调用[super load]
1 |
|
Aspect Oriented Programming
AOP作用就是分离横向关注点(Cross-cutting concern)来提高模块复用性,它可以在既有的代码添加一些额外的行为(记录日志、身份验证、缓存)而无需修改代码。
Isa Swizzling
本质上也是交换,不过交换的是Isa。官方库里面有一个很有名的技术就用到了Isa Swizzling,那就是KVO——Key-Value Observing。
KVO是为了监听一个对象的某个属性值是否发生变化。在属性值发生变化的时候,肯定会调用其setter方法。所以KVO的本质就是监听对象有没有调用被监听属性对应的setter方法。在执行完addObserver: forKeyPath: options: context: 方法之后,把isa指向到另外一个类去。在这个新类里面重写被观察的对象四个方法。class,setter,dealloc,_isKVOA。
重写class方法是为了我们调用它的时候返回跟重写继承类之前同样的内容。
在新的类中会重写对应的set方法,是为了在set方法中增加另外两个方法的调用:
1 | - (void)willChangeValueForKey:(NSString *)key |
重写dealloc方法,销毁新生成的NSKVONotifying_类。
所以,永远不要用用isa来判断一个类的继承关系,而是应该用class方法来判断类的实例。
Associated Object关联对象
我们可以通过Associated Object来实现在 Category 中添加属性的功能。
1 | // NSObject+AssociatedObject.h |
动态的增加方法
在消息发送阶段,如果在父类中也没有找到相应的IMP,就会执行resolveInstanceMethod方法。在这个方法里面,可以动态的给类对象或者实例对象动态的增加方法。
1 | + (BOOL)resolveInstanceMethod:(SEL)sel { |
NSCoding的自动归档和自动解档
用runtime实现的思路就比较简单,我们循环依次找到每个成员变量的名称,然后利用KVC读取和赋值就可以完成encodeWithCoder和initWithCoder了。
1 |
|
字典和模型互相转换
字典转模型
- 调用
class_getProperty方法获取当前Model的所有属性。 - 调用
property_copyAttributeList获取属性列表。 - 根据属性名称生成
setter方法。 - 使用
objc_msgSend调用setter方法为Model的属性赋值(或者KVC)
1 | +(id)objectWithKeyValues:(NSDictionary *)aDictionary{ |
模型转字典
- 调用
class_copyPropertyList方法获取当前Model的所有属性。 - 调用
property_getName获取属性名称。 - 根据属性名称生成
getter方法。 - 使用
objc_msgSend调用getter方法获取属性值(或者KVC)
1 | //模型转字典 |
缺点
Method swizzling不是原子性操作。如果在+load方法里面写,是没有问题的,但是如果写在+initialize方法中就会出现一些奇怪的问题。
可能导致命名冲突。
调用顺序对于Swizzling来说,很重要。
难以理解。
难以调试。