Objective-C runtime - 消息
前言
系列第三篇:来看看OC的消息。包括方法查找、缓存、方法决议以及消息转发
objc_msgSend
objc_msgSend
函数的原型为void objc_msgSend(id self, SEL cmd, ...)
给一个对象发送消息:
1 | id returnValue = [someObject messageName:parameter]; |
编译器会将该OC语句转换成objc_msgSend
函数:
1 | id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter); |
其中的SEL选择子可以理解成方法的名字。
从这里可以看出,给OC对象发送消息,其实就是调用objc_msgSend
函数,所有这篇文章的重点也是该函数的具体实现过程
OC方法本质
1 | - (void)hello:(int)a; |
当有一个对象调用该方法时:
1 | [someObj hello:2]; |
方法查找 && 缓存
回忆一下objc_class
结构体
类的方法都存放在bits
成员变量中,而方法的缓存则是存放在cache
成员变量中。
objc_msgSend
函数通过调用lookUpImpOrForward
函数来查找方法的实现
当前类查询
objc_msgSend
函数查询方法的实现,先会在当前类中查询。
- 先在缓存
cache
中寻找是否存在该方法,找到就去执行 - 如果没在缓存中找到该方法,就到
bits
中寻找方法。如果找到,会先写入缓存cache
,再去执行方法;如果没找到,就会去super_class
里面寻找
沿着类继承关系查询
每个objc_class
都会有指向父类的指针。当子类无法提供方法的实现时,会跑到父类来寻找该实现,过程相似:
- 在父类的缓存
cache
中寻找。如果找到,写到调用该方法的类的cache
中,再执行 - 在缓存中没找到,就继续在父类的
bits
中查找,找到,写入缓存并执行 - 如果还是没找到,继续沿着类继承关系寻找方法的实现
方法决议
+ (BOOL)resolveInstanceMethod:(SEL)selector
+ (BOOL)resolveClassMethod:(SEL)selector
如果在当前类以及所有父类之中都无法找到方法的实现的话,就进入了方法决议的阶段。来看看方法决议的过程:(该过程的源码也在lookUpImpOrForward
方法中)
查看当前类是否实现了resolve方法。如果没有,决议直接失败
如果存在resolve方法,那么执行该方法。这个方法里面一般会动态给当前类添加方法,是的之前没有找到实现的方法可以实现。例如,来实现@dynamic的属性的存取方法
1 | // 注意:以下两方法的相关实现代码已经写好 |
在这一个resolve方法里面,给类新增加了一个方法。
- 执行完resolve方法之后,会重新进行一次方法的查找。如果找到方法了,执行
消息转发
如果方法决议也没有用的话,会给这个方法最后一个机会:消息转发
备援接收者
- (id)forwardingTargetForSelector:(SEL)selector;
执行该函数,看能否返回一个新的对象,这个对象可以处理该方法。具体参见官方文档
完整的消息转发
如果无法找到备胎的话,就要进行完整的消息转发了。将未处理的消息封装成NSInvocation
对象,然后调用- (void)forwardInvocation:(NSInvocation *)invocation;
方法。
这个转发的方法属于 NSObject
,需要子类重写该方法。具体参见官方文档
完整的消息转发图示
其他
在后续的一片文章中会有详细的示例来说明消息决议和转发
参考
- 从源代码看 ObjC 中消息的发送
- Effective Objective-C 2.0
- forwardingTargetForSelector:
- forwardInvocation: