作者:代培
地址:http://daipei.me/posts/source_code_learning_of_runtime_imp/
转载请注明出处
我的博客搬家了,新博客地址:daipei.me
写在前面
前段时间写了一篇博客runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法),这是在看《招聘一个靠谱的iOS》时回答第22题时总结的一篇博客,不过这篇博客中并没有牵涉到底层的代码,而且也留下了几个没有解决的问题,这篇博客将深入runtime源码继续探索这个问题,并尝试解决上篇博客中未解决的问题,本人第一次阅读源码,如果有分析错误的地方,欢迎大家纠正。
引入
首先大家都知道,在oc中调用方法(或者说发送一个消息是)runtime底层都会翻译成objc_msgSend(id self, SEL op, ...)
,苹果为了优化性能,这个方法是用汇编写成的
/********************************************************************
*
* id objc_msgSend(id self, SEL _cmd,...);
*
********************************************************************/
ENTRY objc_msgSend
# check whether receiver is nil
teq a1, #0
beq LMsgSendNilReceiver
# save registers and load receiver's class for CacheLookup
stmfd sp!, {a4,v1}
ldr v1, [a1, #ISA]
# receiver is non-nil: search the cache
CacheLookup a2, v1, LMsgSendCacheMiss
# cache hit (imp in ip) and CacheLookup returns with nonstret (eq) set, restore registers and call
ldmfd sp!, {a4,v1}
bx ip
# cache miss: go search the method lists
LMsgSendCacheMiss:
ldmfd sp!, {a4,v1}
b _objc_msgSend_uncached
LMsgSendNilReceiver:
mov a2, #0
bx lr
LMsgSendExit:
END_ENTRY objc_msgSend
实话说我没有学过汇编,所以看到这段代码我的内心是崩溃的,更可怕的是针对不同的平台,还有不同汇编代码的实现
虽然不懂汇编,但是苹果的注释很详细,看注释也可以大致明白在干什么,首先检查传入的self是否为空,然后根据selector寻找方法实现IMP,找到则调用并返回,否则抛出异常。由此可以有以下伪代码
id objc_msgSend(id self, SEL _cmd, ...) {
Class class = object_getClass(self);
IMP imp = class_getMethodImplementation(class, _cmd);
return imp ? imp(self, _cmd, ...) : 0;
}
伪代码中我们看到class_getMethodImplementation(Class cls, SEL sel)
方法用来寻找IMP地址,有趣的是苹果真的提供了这个方法,可以让我们调用,通过selector去寻找方法实现IMP,而这个函数的实现,以及其延伸就是这篇博客所要探讨的重点。
正文
在我前面的文章中也说到IMP寻址总共有两种方法:
IMP class_getMethodImplementation(Class cls, SEL name);
IMP method_getImplementation(Method m);
而在NSObject中提供了几个对class_getMethodImplementation封装的方法
+ (IMP)instanceMethodForSelector:(SEL)sel {
if (!sel) [self doesNotRecognizeSelector:sel];
return class_getMethodImplementation(self, sel);
}
+ (IMP)methodForSelector:(SEL)sel {
if (!sel) [