在iOS-慢速方法查找和iOS-快速方法查找中我們分別提到了objc_msgSend
的快速查找和慢速查找袍暴,如果經(jīng)歷這兩步仍未找到該方法的imp
會怎么樣呢佛呻?
Apple給了我們兩次補(bǔ)救的機(jī)會,
動態(tài)方法決議:慢速查找流程未找到后,會執(zhí)行一次動態(tài)方法決議
消息轉(zhuǎn)發(fā):如果動態(tài)方法決議仍然沒有找到實現(xiàn),則進(jìn)行消息轉(zhuǎn)發(fā)
如果這兩次機(jī)會都沒有做任何操作,就會報我們?nèi)粘i_發(fā)中常見的方法未實現(xiàn)的崩潰報錯——unrecognized selector sent to
。
我們來看看是怎么從慢速查找進(jìn)入到第一個機(jī)會——動態(tài)方法決議的吧跨扮。
康康該方法的源碼
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
//對象 -- 類
if (! cls->isMetaClass()) { //類不是元類,調(diào)用對象的解析方法
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {//如果是元類验毡,調(diào)用類的解析方法衡创, 類 -- 元類
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
//為什么要有這行代碼? -- 類方法在元類中是對象方法晶通,所以還是需要查詢元類中對象方法的動態(tài)方法決議
if (!lookUpImpOrNil(inst, sel, cls)) { //如果沒有找到或者為空璃氢,在元類的對象方法解析方法中查找
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
//如果方法解析中將其實現(xiàn)指向其他方法,則繼續(xù)走方法查找流程
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
先看看對象方法的動態(tài)決議源碼
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))) {//查找resolveInstanceMethod是否實現(xiàn)狮辽,其實源碼中NSObject實現(xiàn)了該方法一也。
// Resolver not implemented.
return;
}
//消息轉(zhuǎn)發(fā)巢寡,執(zhí)行resolveInstanceMethod方法
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));
}
}
}
我們來代碼驗證一下,給LGPerson.h
添加如下代碼
@interface LGPerson : NSObject
+ (void)say1;//只聲明椰苟,未實現(xiàn)
+ (void)say4;
- (void)say2;
- (void)say3;//只聲明抑月,未實現(xiàn)
@end
給LGPerson.m
添加如下代碼
@implementation LGPerson
//- (void)say1
//{
// NSLog(@"%s",__func__);
//}
- (void)say2
{
NSLog(@"%s",__func__);
}
//- (void)say3
//{
// NSLog(@"%s",__func__);
//}
+ (void)say4
{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(say3)) {
NSLog(@"%@ 來了", NSStringFromSelector(sel));
}
return [super resolveInstanceMethod:sel];
}
@end
在 main
中調(diào)用say3
,執(zhí)行結(jié)果如下:
可以看到
resolveInstanceMethod
執(zhí)行了兩次,why舆蝴?-第一次的“來了”是在查找say3方法時會進(jìn)入動態(tài)方法決議
-第二次“來了”是在慢速轉(zhuǎn)發(fā)流程中調(diào)用了CoreFoundation框架中的NSObject(NSObject) methodSignatureForSelector:后爪幻,會再次進(jìn)入動態(tài)決議
可通過
lldb
的bt
命令看看堆棧信息驗證如果想抓住這個防止崩潰的機(jī)會,可以修改resolveInstanceMethod
代碼如下
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(say3)) {
NSLog(@"%@ 來了", NSStringFromSelector(sel));
//獲取say2方法的imp
IMP imp = class_getMethodImplementation(self, @selector(say2));
//獲取say2的實例方法
Method sayMethod = class_getInstanceMethod(self, @selector(say2));
//獲取say2的簽名
const char *type = method_getTypeEncoding(sayMethod);
//將sel的實現(xiàn)指向say2
return class_addMethod(self, sel, imp, type);
}
//
return [super resolveInstanceMethod:sel];
}