objc-runtime 开源地址
消息转发
在开启消息转发之前,先来看看定义在NSObject.h
中的这五个方法:
// 阶段一
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
// 阶段二
- (id)forwardingTargetForSelector:(SEL)aSelector;
// 阶段三
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
我们可以把runtime想象成一个方法处理工厂,当这个工厂遇到自身能力处理不了的"原料"时,它并不会直接把"原料"当成废料抛弃掉。而是把它们送入一段传送带(消息转发)去处理,当然这也是最后的阶段,如果这一阶段仍然没有接受者愿意且有能力处理它们,那么,runtime会抛出异常
(void)doesNotRecognizeSelector:(SEL)sel ;
在runtime的"寻根"之旅结束后。如果没有找到与之对应的IMP,则会进入第一阶段。
+ (BOOL)resolveClassMethod:(SEL)sel; // 对应类方法
+ (BOOL)resolveInstanceMethod:(SEL)sel; // 对应实例方法
具体我们可以在源码中看到:
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
注释已经告诉了我们No implementation found. Try method resolver once. (如果没有找到方法的实现,尝试调用一次方法决议) 。
那么我们来一步一步的分析resolve Method的实现历程:
- 首先由两个状态标识,
resolver && !triedResolver
来判断是否需要调用方法决议。resolver
只会在_class_lookupMethodAndLoadCache3
也就是消息调用的时候才是YES
,而triedResolver
保证了不会无限循环,这里比较简单。
- 接下来我们看
_class_resolveMethod
这个函数:
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
可以看出,源码里通过判断当前Class是否是MetaClass决定调用resolveInstanceMethod
还是resolveClassMethod
,类方法和实力方法的处理是不一样的。
- 且先看一看
_class_resolveInstanceMethod
这个函数:
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
}
过程很简单,先查找SEL_resolveInstanceMethod
方法是否实现,SEL_resolveInstanceMethod
是一个SEL,它_read_images
之前,就已经通过sel_registerNameNoLock
被赋值。如果有此方法的实现,则通过objc_msgSend
调用。
之后再次调用了IMP lookUpImpOrNil(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)
函数。至于意义,我们要归根到,resolveInstanceMethod
方法的意义。
runtime消息转发的每个阶段需要带给我们的权限都不同,第一个阶段,我们能够做的,仅是通过runtime的class_addMethod
函数在运行时添加一个SEL对应的IMP。
当我们成功在方法决议中动态添加了Method之后,IMP lookUpImpOrNil(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)
的意义也就不难理解了。
Cache the result (good or bad) so the resolver doesn't fire next time.
-
类方法的处理其实和实例方法的处理大致相同,只是这里有个疑问,笔者也没有想明白:
为什么在调用了_class_resolveClassMethod函数之后还要调用_class_resolveInstanceMethod ? 大雾~~~
在进行完上述操作之后,runtime会返回再次查找方法(这次是查找动态添加的方法实现),如果仍然没有找到,那么会进入消息转发的第二个阶段:
Use forwarding
如果说forwarding是转发的话,第一阶段只能称之为方法动态决议,毕竟第一阶段并没有实现转发这一功能。
第二阶段调用的方法是:
- (id)forwardingTargetForSelector:(SEL)aSelector;
这里需要我们返回的是一个方法的备用接受者,类似于那个工厂的例子。我们需要手动在runtime时期为这个方法去提供一个接受者,这个接受者有能力去执行这个方法。
完备接受转发和消息派发,由于设计的汇编知识较多,可以Mark这两篇blog。
Objective-C 消息发送与转发机制原理
消息转发