objc_msgSend
,它的伪代码如下或类似的逻辑,获取 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
的实现如下:
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
imp = lookUpImpOrNil(cls, sel, nil, YES/*initialize*/, YES/*cache*/, YES/*resolver*/);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}
lookUpImpOrNil
的实现如下:
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
使用 lookUpImpOrForward 快速查找 IMP
lookUpImpOrForward
的实现见 objc-runtime-new.mm 4587行,大致逻辑如下:
- 如果 selector 是需要被忽略的垃圾回收用到的方法,则将 IMP 结果设为
_objc_ignored_method
,这是个汇编程序入口,可以理解为一个标记。对此种情况进行缓存填充操作后,跳到第 7 步;否则执行下一步。 - 查找当前类中的缓存,跟之前一样,使用
cache_getImp
汇编程序入口。如果命中缓存获取到了 IMP,则直接跳到第 7 步;否则执行下一步。 - 在当前类中的方法列表(method list)中进行查找,也就是根据 selector 查找到 Method 后,获取 Method 中的 IMP(也就是 method_imp 属性),并填充到缓存中。查找过程比较复杂,会针对已经排序的列表使用二分法查找,未排序的列表则是线性遍历。如果成功查找到 Method 对象,就直接跳到第 7 步;否则执行下一步。
- 在继承层级中递归向父类中查找,情况跟上一步类似,也是先查找缓存,缓存没中就查找方法列表。这里跟上一步不同的地方在于缓存策略,有个
_objc_msgForward_impcache
汇编程序入口作为缓存中消息转发的标记。也就是说如果在缓存中找到了 IMP,但如果发现其内容是_objc_msgForward_impcache
,那就终止在类的继承层级中递归查找,进入下一步;否则跳到第 7 步。 - 当传入
lookUpImpOrForward
的参数 resolver 为 YES 并且是第一次进入第 5 步时,时进入动态方法解析;否则进入下一步。这步消息转发前的最后一次机会。此时释放读入锁(runtimeLock.unlockRead()),接着间接地发送 +resolveInstanceMethod 或 +resolveClassMethod 消息。这相当于告诉程序员『赶紧用 Runtime 给类里这个 selector 弄个对应的 IMP 吧』,因为此时锁已经 unlock 了所以不会缓存结果,甚至还需要软性地处理缓存过期问题可能带来的错误。这里的业务逻辑稍微复杂些,后面会总结。因为这些工作都是在非线程安全下进行的,完成后需要回到第 1 步再次查找 IMP。 - 此时不仅没查找到 IMP,动态方法解析也不奏效,只能将
_objc_msgForward_impcache
当做 IMP 并写入缓存。这也就是之前第 4 步中为何查找到_objc_msgForward_impcache
就表明了要进入消息转发了。 - 读操作解锁,并将之前找到的 IMP 返回。(无论是正经 IMP 还是不正经的
_objc_msgForward_impcache
)
objc_msgForward_impcache 的转换
上一节最后讲到如果没找到 IMP,就会将 _objc_msgForward_impcache
返回到 objc_msgSend
函数,而正是因为它是用汇编语言写的,所以将内部使用的 _objc_msgForward_impcache
转化成外部可调用的 _objc_msgForward
或 _objc_msgForward_stret
也是由汇编代码来完成。
_objc_msgForward*
系列本质都是函数指针,都用汇编语言实现,都可以与 IMP 类型的值作比较。
objc_msgForward 也只是个入口
从汇编源码可以看出 _objc_msgForward
和 _objc_msgForward_stret
会分别调用 _objc_forward_handler
和 _objc_forward_handler_stret
也就是说,消息转发过程是现将 _objc_msgForward_impcache
强转成 _objc_msgForward
或 _objc_msgForward_stret
,再分别调用 _objc_forward_handler
或 _objc_forward_handler_stret
。
在 Objective-C 2.0 之前,默认的 _objc_forward_handler
或 _objc_forward_handler_stret
都是 nil,而新版本的默认实现是这样的:
// Default forward handler halts the process.
__attribute__((noreturn)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
#if SUPPORT_STRET
struct stret { int i[100]; };
__attribute__((noreturn)) struct stret
objc_defaultForwardStretHandler(id self, SEL sel)
{
objc_defaultForwardHandler(self, sel);
}
void *_objc_forward_stret_handler = (void*)objc_defaultForwardStretHandler;
#endif
objc_defaultForwardHandler
中的 _objc_fatal
作用就是打日志并调用 __builtin_trap()
触发 crash,可以看到我们最熟悉的那句 “unrecognized selector sent to instance” 日志。__builtin_trap()
在杀掉进程的同时还能生成日志,比调用 exit() 更好。
因为默认的 Handler 干的事儿就是打日志触发 crash,我们想要实现消息转发,就需要替换掉 Handler 并赋值给 _objc_forward_handler
或 _objc_forward_handler_stret
,赋值的过程就需要用到 objc_setForwardHandler
函数,实现也是简单粗暴,就是赋值啊:
void objc_setForwardHandler(void *fwd, void *fwd_stret)
{
_objc_forward_handler = fwd;
#if SUPPORT_STRET
_objc_forward_stret_handler = fwd_stret;
#endif
}
重头戏在于对 objc_setForwardHandler
的调用,以及之后的消息转发调用栈。这里涉及汇编及反编译,不细说这个,想看的可以在参考资料里面去细看。这里大概逻辑就是CoreFoundation会调用objc_setForwardHandler
设置handle,设置的handle会调用___forwarding___
,___forwarding___
函数的伪代码实现如下:
int __forwarding__(void *frameStackPointer, int isStret) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 8);
const char *selName = sel_getName(sel);
Class receiverClass = object_getClass(receiver);
// 调用 forwardingTargetForSelector:
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget && forwarding != receiver) {
if (isStret == 1) {
int ret;
objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
return ret;
}
return objc_msgSend(forwardingTarget, sel, ...);
}
}
// 僵尸对象
const char *className = class_getName(receiverClass);
const char *zombiePrefix = "_NSZombie_";
size_t prefixLen = strlen(zombiePrefix); // 0xa
if (strncmp(className, zombiePrefix, prefixLen) == 0) {
CFLog(kCFLogLevelError,
@"*** -[%s %s]: message sent to deallocated instance %p",
className + prefixLen,
selName,
receiver);
}
// 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
if (methodSignature) {
BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
if (signatureIsStret != isStret) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.",
selName,
signatureIsStret ? "" : not,
isStret ? "" : not);
}
if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
[receiver forwardInvocation:invocation];
void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
} else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
receiver,
className);
return 0;
}
}
}
SEL *registeredSel = sel_getUid(selName);
// selector 是否已经在 Runtime 注册过
if (sel != registeredSel) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
sel,
selName,
registeredSel);
} // doesNotRecognizeSelector
else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
[receiver doesNotRecognizeSelector:sel];
}
else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
receiver,
className);
}
// The point of no return.
kill(getpid(), 9);
}
参考资料:
Objective-C 消息发送与转发机制原理
Objective-C 函数节流及函数防抖