上篇文章中我们分析了objc_msgSend
的快速查找流程和慢速查找流程。在慢速查找流程中,我们知道objc_msgSend
消息查找在lookUpImpOrForward
方法中从父类递归去查找方法,直到找到imp
。但是如果一直查找到父类为nil
的时候还没有找到方法的实现,此时imp
赋值为由_objc_msgForward_impcache
的函数生成的forward_imp
。那么今天我们就来分析一下这种条件下的方法查找处理逻辑。
方法的动态决议
1、resolveMethod_locked
只执行一次的逻辑
首先我们还是先去objc
源码里面去找一下_objc_msgForward_impcache
函数的实现。从源码中我们发现_objc_msgForward_impcache
函数也是通过汇编写的,其内部只调用了objc_msgForward
的函数
objc_msgForward
的函数的函数内部有2行代码都是调用了objc_forward_handler
的函数,最后调用了TailCallFunctionPointer
函数,TailCallFunctionPointer
函数在上篇文章中我们查看源码得知它其实仅仅只是一个函数指针的调用。那么我们去看一下objc_forward_handler
函数里面做了什么样的操作
我们看到objc_forward_handler
= objc_defaultForwardHandler
,而objc_defaultForwardHandler
函数值做了方法找不到的报错反馈,其中用来区分类方法还是实例方法的条件仅仅是判断当前接收的对象是否是元类。又一方面证明了objc底层并没有去区分类方法和实例方法
。了解了_objc_msgForward_impcache
函数之后我们在lookUpImpOrForward
方法中可以看到有以下几行代码
从注释当中我们也可以知道,如果imp没有找到,他就会执行这几行代码去尝试解析方法一次。且只会执行一次,那么我们先看一下它是怎么实现只执行一次的逻辑的。首先在objc
源码的main.m文件中添加以下调试代码
@interface MyClass : NSObject
- (void)method1;
@end
@implementation MyClass
@end
复制代码
在MyClass
类里面声明一个没有实现的方法method1
,运行objc
源码当执行到[p method1]
时在lookUpImpOrForward
中也打个断点

继续执行到lookUpImpOrForward
方法中的断点处,也就是第一次执行进入lookUpImpOrForward
方法时,此时behavior = 3
,而LOOKUP_RESOLVER = 2
那么if条件判断就等于3(11) & 2(10) = 2(10)
为真,条件成立,此时会执行if内的代码,此时behavior ^= LOOKUP_RESOLVER
(异或运算:相等为0,不等为1),也就是behavior = 3(11) ^ 2(10) = 1(01)
,并返回执行resolveMethod_locked
函数的结果
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
复制代码
从resolveMethod_locked
函数内的操作来看,前面执行的是方法的动态决议,在动态决议之后最后会执行lookUpImpOrForwardTryCache
函数,进入函数内部查看
IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior);
}
复制代码
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
if (slowpath(!cls->isInitialized())) {
// see comment in lookUpImpOrForward
return lookUpImpOrForward(inst, sel, cls, behavior);
}
IMP imp = cache_getImp(cls, sel);
if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
}
#endif
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
复制代码
lookUpImpOrForwardTryCache
函数会调用lookUpImpTryCache
函数,在lookUpImpTryCache
函数内我们发现在方法的动态决议之后,系统还会去cache
里面找一遍方法,如果没有找到,即imp == NULL
,此时又会再次执行lookUpImpOrForward
方法,这时传入的behavior = 1
,当第二次进入lookUpImpOrForward
方法内断点处的if条件为1(01) & 2(10) = 0(00)
,条件不成立,不会执行if语句内的代码,所以它就实现只执行一次的逻辑。
2、动态决议
了解了这块代码只执行一次的逻辑之后,我们去分析一下resolveMethod_locked
函数内方法的动态决议部分,在resolveMethod_locked
方法内,我们看到系统会调用resolveInstanceMethod
和resolveClassMethod
这两个方法。我们可以在类里面去重写resolveInstanceMethod和resolveClassMethod方法来为没有实现的方法通过runtime的API动态的添加方法的实现(为sel动态的添加imp)
。那么我们分别去看一下这两个方法内部的实现
实例方法的动态决议resolveInstanceMethod
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, 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 = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
复制代码
在方法内部我们看到就是objc_msgSend
函数调用了resolveInstanceMethod
这个类方法,接下来我们就在MyClass
类里面去实现一下这个方法
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"%s--%@", __func__, NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
@end
复制代码
运行之后出现以下崩溃信息
从崩溃信息中看到在method1
方法没有实现的时候(找不到方法的IMP),在调用崩溃信息之前,就执行了resolveInstanceMethod
方法,并且执行了两次
。也就是说在方法找不到的实现(IMP)的时候,系统会调用resolveInstanceMethod
方法。那么我们就可以在这个方法内通过runtime的API
来动态的给这个sel
添加一个imp
。
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"%s--%@", __func__ , NSStringFromSelector(sel));
if (sel == @selector(method1)) {
IMP imp = class_getMethodImplementation(self.class, @selector(method2));
class_addMethod(self.class, sel, imp, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
- (void)method2 {
NSLog(@"%s", __func__ );
}
@end
复制代码
再次运行代码我们可以看到,当method1
方法没有实现的时候,我们可以在resolveInstanceMethod
方法里面为这个method1
方法动态的添加一个method2
的实现。在调用method1
方法的时候系统就会去调用method2
的实现。实例方法没有实现的时候我们就可以通过这种方法为这个sel
添加一个imp
。
同时在objc
源码里resolveInstanceMethod
方法内部在我们通过这个方法给sel
添加一个imp
之后,系统还会调用lookUpImpOrNilTryCache
这个方法去cache
里面找imp
,那么此时系统能在cache
里面找到这个方法的imp
吗?也就是说我们为sel
动态添加方法的实现之后,这个方法会被缓存到cache
里面吗?我们去研究一下
执行代码到断点处,为了防止cache
的扩容机制导致method1
的丢失,我们通过lldb调试命令为MyClass
的cache
里面添加两个方法,让系统先进行cache
扩容。(关于cache的相关内容可以去cache_t详解中了解)
此时断点移到[p method1];
下面后,去执行method1
方法。这时候我们再去cache
里面查找一下看有没有method1
方法
我们可以看到在caceh
里面缓存了method1
这个方法,也就是说通过resolveInstanceMethod
方法动态的为sel
添加imp
的时候,同样会添加在cache
缓存里
类方法的动态决议resolveClassMethod
如果是实例方法系统在resolveMethod_locked
方法内会调用resolveInstanceMethod
方法,如果是类方法,那么系统会调用resolveClassMethod
这个方法进行方法的动态决议
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
复制代码
同resolveInstanceMethod
方法一样,在resolveClassMethod
方法内部,系统一样通过objc_msgSend
函数调用resolveClassMethod
这个方法进行类方法的动态决议,之后调用lookUpImpOrNilTryCache
方法从cache
里面去找imp
。同样,在MyClass
类里面声明一个没有方法实现的类方法test
,并实现resolveClassMethod
方法
@interface MyClass : NSObject
- (void)method1;
+ (void)test;
@end
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"%s--%@", __func__ , NSStringFromSelector(sel));
if (sel == @selector(method1)) {
IMP imp = class_getMethodImplementation(self.class, @selector(method2));
class_addMethod(self.class, sel, imp, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
- (void)method2 {
NSLog(@"%s", __func__ );
}
+ (BOOL)resolveClassMethod:(SEL)sel {
NSLog(@"%s--%@", __func__ , NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
@end
复制代码
执行[MyClass test];
之后,同样在打印崩溃信息之前系统也调用了2
次resolveClassMethod
方法
那么同理在resolveClassMethod
方法内部我们也可以为类方法test
动态添加方法的实现
+ (BOOL)resolveClassMethod:(SEL)sel {
NSLog(@"%s--%@", __func__ , NSStringFromSelector(sel));
if (sel == @selector(test)) {
IMP imp = class_getMethodImplementation(self.class, @selector(method3));
class_addMethod(objc_getMetaClass("MyClass"), sel, imp, "v@:");
return YES;
}
return [super resolveClassMethod:sel];
}
- (void)method3 {
NSLog(@"%s", __func__ );
}
复制代码
那么如果我们对resolveClassMethod
和resolveInstanceMethod
方法做以下调整之后
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"%s--%@", __func__ , NSStringFromSelector(sel));
// if (sel == @selector(method1)) {
IMP imp = class_getMethodImplementation(self.class, @selector(method2));
class_addMethod(self.class, sel, imp, "v@:");
return YES;
// }
// return [super resolveInstanceMethod:sel];
}
- (void)method2 {
NSLog(@"%s", __func__ );
}
+ (BOOL)resolveClassMethod:(SEL)sel {
NSLog(@"%s--%@", __func__ , NSStringFromSelector(sel));
// if (sel == @selector(test)) {
IMP imp = class_getMethodImplementation(self.class, @selector(method4));
class_addMethod(objc_getMetaClass("MyClass"), sel, imp, "v@:");
return YES;
// }
// return [super resolveClassMethod:sel];
}
- (void)method3 {
NSLog(@"%s", __func__ );
}
@end
复制代码
再执行[MyClass test];
我们发现竟然最后调用了method2
的方法实现
出现这个现象的原因其实也好理解,因为在resolveClassMethod
方法动态的添加方法实现的时候我们调用了class_getMethodImplementation
这个方法去找method4
的方法实现,传入的是类对象(self.class
)。这个方法同样会进入resolveMethod_locked
方法里面,此时在判断if (! cls->isMetaClass())
(当前传入的cls
是否不是元类)的时候条件为真,这时候系统就会走到resolveInstanceMethod
这个方法里面去。所以最终调用的是method2
的方法实现。如果把传入的类对象self.class
改成传入元类对象objc_getMetaClass("MyClass")
,系统在条件判断之后就会一直调用resolveClassMethod
方法。此时就会出现死循环。
在resolveMethod_locked
方法内,我们还看到系统在调用resolveClassMethod
类方法的动态决议方法之后,系统会调用lookUpImpOrNilTryCache
这个方法去cache
里面找一遍方法的实现,如果在cache
里面没有找到方法的实现。系统还会去调用resolveInstanceMethod
这个实例方法的动态决议方法
`系统这样做的原因`
通过继承链的关系我们知道类方法是存放在元类里面,而元类最终会继承自NSObject类,如果没有
`resolveClassMethod`这个动态决议方法,系统在查找类方法的时候会走`resolveInstanceMethod`
这个动态决议方法,这样的查找流程就会比较的长,会影响系统查找的效率。基于这个原因系统提供了
`resolveClassMethod`方法来给类方法动态添加方法的实现,用来简化类方法的查找流程而提供给我们去实现
的一个方法而已。系统其实最终都需要通过`resolveInstanceMethod`方法来进行方法的动态决议。
复制代码
AOP
- 实例1
为NSObject添加一个分类FL
,在分类里面实现resolveInstanceMethod
方法,来解决方法找不到的崩溃问题(提高程序的稳定性)或对没有实现的方法的收集(crash收集
)等
@interface NSObject (FL)
@end
@implementation NSObject (FL)
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"%s--%@", __func__ , NSStringFromSelector(sel));
// if (sel == @selector(method1)) {
IMP imp = class_getMethodImplementation(self.class, @selector(method2));
class_addMethod(self.class, sel, imp, "v@:");
return YES;
// }
// return [super resolveInstanceMethod:sel];
}
- (void)method2 {
NSLog(@"%s", __func__ );
}
@end
复制代码
- 实例2
通过runtime的API进行数据埋点。例如记录页面的停留时间,其中记录进入页面的时间核心代码如下
@implementation UIViewController (FL)
+(void)load
{
static dispatch_once_t oncet;
dispatch_once(&oncet, ^{
Method method1 = class_getInstanceMethod(self.class, @selector(viewWillAppear:));
Method method2 = class_getInstanceMethod(self.class, @selector(aopviewWillAppear));
method_exchangeImplementations(method1, method2);
});
}
-(void)aopviewWillAppear
{
//在这里可以记录进入页面的时间
NSLog(@"进来了 %@",self.class);
//通过方法交换执行的是viewWillAppear,不会造成死循环
[self aopviewWillAppear];
}
@end
复制代码
消息转发
在lookUpImpOrForward
方法中找到方法的imp
之后会跳转到done
执行log_and_fill_cache
方法来进行方法的缓存
done:
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
复制代码
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cls->cache.insert(sel, imp, receiver);
}
复制代码
在进行方法的缓存操作cls->cache.insert
之前,还有一个条件判断objcMsgLogEnabled && implementer
去执行logMessageSend
方法向/tmp/msgSends
文件里面写入信息
bool objcMsgLogEnabled = false;
static int objcMsgLogFD = -1;
bool logMessageSend(bool isClassMethod,
const char *objectsClass,
const char *implementingClass,
SEL selector)
{
char buf[ 1024 ];
// Create/open the log file
if (objcMsgLogFD == (-1))
{
snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
if (objcMsgLogFD < 0) {
// no log file - disable logging
objcMsgLogEnabled = false;
objcMsgLogFD = -1;
return true;
}
}
// Make the log entry
snprintf(buf, sizeof(buf), "%c %s %s %s\n",
isClassMethod ? '+' : '-',
objectsClass,
implementingClass,
sel_getName(selector));
objcMsgLogLock.lock();
write (objcMsgLogFD, buf, strlen(buf));
objcMsgLogLock.unlock();
// Tell caller to not cache the method
return false;
}
复制代码
那么要想执行写入信息的方法,条件判断中objcMsgLogEnabled
要为真,因为implementer
是传入的类,它一定是存在且有值的。其中objcMsgLogEnabled
默认是为false
。我们在objc
源码里去搜索一下objcMsgLogEnabled
看哪些地方有给它赋值。搜索发现只有在instrumentObjcMessageSends
方法里面才有可能会给objcMsgLogEnabled
赋值为true
void instrumentObjcMessageSends(BOOL flag)
{
bool enable = flag;
// Shortcut NOP
if (objcMsgLogEnabled == enable)
return;
// If enabling, flush all method caches so we get some traces
if (enable)
_objc_flush_caches(Nil);
// Sync our log file
if (objcMsgLogFD != -1)
fsync (objcMsgLogFD);
objcMsgLogEnabled = enable;
}
复制代码
既然如此,那我们可以把这个函数用extern
关键字导出出来,然后通过调用instrumentObjcMessageSends
方法来主动向/tmp/msgSends
下的文件内写入信息
运行代码之后,当MyClass
类里面没有实现test
方法的时候,/tmp/msgSends
下的文件内写入的信息如下
从这个日志信息里面可以看出在调用resolveInstanceMethod
动态决议方法之后和调用doesNotRecognizeSelector
崩溃方法之前系统还调用了forwardingTargetForSelector
和methodSignatureForSelector
这两个方法,那么这两个方法执行的就是传说中的消息转发
1、消息的快速转发forwardingTargetForSelector
我们同样在MyClass
类里面去重写这个方法
@interface MyClass : NSObject
-(void)test;
@end
@implementation MyClass
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s", __func__ );
return nil;
}
@end
复制代码
当我们去调用MyClass
类里面的test
方法的时候,在崩溃之前系统的确调用了forwardingTargetForSelector
方法
我们在这个方法里面同样可以去做一些处理,当类不能响应某个方法的时候,可以在forwardingTargetForSelector
方法里面去把这个消息转发给能够响应此方法的类,例如再创建一个ForwardingClass
类,这个类里面实现了test
方法
@interface ForwardingClass : NSObject
@end
@implementation ForwardingClass
-(void)test {
NSLog(@"%s", __func__ );
}
@end
复制代码
然后在forwardingTargetForSelector
方法里面去把test这个消息转发给ForwardingClass
类
@implementation MyClass
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s", __func__ );
if (aSelector == @selector(test)) {
return [ForwardingClass new];
}
return nil;
}
@end
复制代码
此时再调用MyClass
类的实例方法test
就不会产生崩溃了。既然test
已经转发给ForwardingClass
类,那么此时test
方法就会缓存在ForwardingClass
类的cache
里面。因为转发之后就不是MyClass
类的实例对象在调用test
方法,而是ForwardingClass
类的实例对象在调用test
方法。
我们可以通过这个方法来把未处理的消息转发给一个单独的类,这个单独的类就可以去统一处理那些其他类里面没有处理的消息,也可以通过这个forwardingTargetForSelector
方法去进行那些方法找不到的crash的收集等。这是对实例对象的方法的快速转发,对于类方法来说也有对应的消息转发的类方法+ (id)forwardingTargetForSelector:(SEL)aSelector
,原理和- (id)forwardingTargetForSelector:(SEL)aSelector
方法相同
2、消息的慢速转发methodSignatureForSelector
如果在消息的快速转发forwardingTargetForSelector
方法里面也没有对消息做处理,那么系统就会调用消息的慢速转发methodSignatureForSelector
方法。我们同样可以在MyClass
类里面去重写methodSignatureForSelector
方法。
@implementation MyClass
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s", __func__ );
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
@end
复制代码
需要注意的是methodSignatureForSelector
方法需要和forwardInvocation
方法一起使用,因为methodSignatureForSelector
方法只是提供了一个方法的有效签名,提供了一个方法的有效签名之后,系统会去调用forwardInvocation
方法来处理这个签名。例如在MyClass
类里面实现forwardInvocation
方法,此时再调用test
就不会产生崩溃了。
@implementation MyClass
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s", __func__ );
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
}
@end
复制代码
同样我们也可以通过把这两个方法写在NSObject
的分类里面来解决方法找不到的崩溃。也可以在forwardInvocation
方法里面去对某个方法去做处理,在NSInvocation
类型的参数anInvocation
里面系统给我们提供了消息的target
(消息接收者)和消息的selector
(消息的方法名)。
例如
- (void)forwardInvocation:(NSInvocation *)anInvocation {
ForwardingClass * t = [ForwardingClass new];
if ([self respondsToSelector:anInvocation.selector]) {
//如果自己能够响应anInvocation中的selector,那么就自己响应这个方法
[anInvocation invokeWithTarget:self];
} else if ([t respondsToSelector:anInvocation.selector]) {
//如果自己响应不了anInvocation中的selector,但是t可以响应,那么我们就把响应这个方法的对象变成t
[anInvocation invokeWithTarget:t];
} else {
//都响应不了anInvocation中的selector
NSLog(@"该功能正在开发中,敬请期待...");
}
}
复制代码
消息的慢速转发(methodSignatureForSelector
和forwardInvocation
方法)、消息的快速转发(forwardingTargetForSelector
方法)和方法的动态决议,就是系统给我们提供的防止崩溃的三根救命稻草。相比于方法的动态决议和消息的快速转发(forwardingTargetForSelector
方法),消息的慢速转发(methodSignatureForSelector
和forwardInvocation
方法)更加灵活,我们甚至可以在forwardInvocation
方法中不做任何操作,系统也不会发生因找不到方法的实现而产生的崩溃。结合上篇内容在OC底层对整个消息的发送流程如下图所示:
以上就是关于方法的动态决议和消息转发的原理
扩展内容
方法的动态决议走两次的原因
resolveInstanceMethod
方法执行两次的原因其实也很简单,我们可以通过在resolveInstanceMethod
加入断点,然后查看堆栈信息就可以知道。
第一次执行:
第一次执行很好理解,结合上篇文章分析,在方法快速查找中查找不到的时候会执行objc_msgSend_uncached
函数,在objc_msgSend_uncached
中又会执行lookUpImpOrForward
函数,然后进入resolveMethod_locked
方法内,最后执行到resolveInstanceMethod
这个方法的动态决议方法内。
第二次执行:
让代码继续向下执行,当第二次进入resolveInstanceMethod
方法内时,我们可以通过bt
指令打印一下此时的堆栈信息
从堆栈信息中我们可以看到,当第一次执行结束之后会进入到_forwarding_
这个函数内,在这个函数内会调用我们前面分析的消息的慢速转发的方法methodSignatureForSelector
,系统在methodSignatureForSelector
方法内会调用class_getInstanceMethod
这个方法。我们去objc
源码里面去找到这个方法的实现
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrForward(**nil**, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
复制代码
我们可以看到在class_getInstanceMethod
方法内有再一次调用了lookUpImpOrForward
方法,在lookUpImpOrForward
方法里面就会再一次调用resolveInstanceMethod
方法。所以这就是resolveInstanceMethod
方法被执行两次的原因。同样resolveClassMethod
方法被执行两次也是这个原因。