第十節(jié)課 消息動(dòng)態(tài)決議
在經(jīng)過我們的快速與慢速查找之后依舊沒有找到的怎么辦呢?
cache_getImp的父類查找流程中LGETImpMissDynamic返回為空的情況下,并沒有直接慢速查找,而是進(jìn)行遞歸查找父類的父類的緩存,都查不到的情況下最終再往下繼續(xù)
unrecognized selector sent to instance
相信大多人都看過這個(gè)錯(cuò)誤吧?如果沒看過當(dāng)我沒說??
調(diào)用未實(shí)現(xiàn)的對(duì)象方法會(huì)報(bào)出這個(gè)錯(cuò)誤,但是我們只知道報(bào)錯(cuò),從來沒有去研究過是怎么報(bào)的這個(gè)錯(cuò)誤,接下來我們就來看看底層報(bào)錯(cuò)的實(shí)現(xiàn)原理
全局搜索lookUpImpOrForward
我們根據(jù)之前的查找流程已經(jīng)知道了,最終如果沒有找到的話,是返回的一個(gè)forward_imp
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
imp = forward_imp;
break;
}
但是具體是什么呢?我們來往上看下賦值部分
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
//進(jìn)入_objc_msgForward_impcache
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
//進(jìn)入__objc_msgForward
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
//進(jìn)入TailCallFunctionPointer x17
.macro TailCallFunctionPointer
// $0 = function pointer value
br $0
.endmacro
//發(fā)現(xiàn)實(shí)際就是br $0,那我們就直接看x17-> __objc_forward_handler
//全局搜索__objc_forward_handler搜不到,就搜索objc_forward_handler
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);
}
我們可以看到實(shí)際輸出的錯(cuò)誤原因就是這里打印的,但是這中間底層實(shí)際做了很多的事情喲,接下來我們就來詳細(xì)的剖析一下
方法動(dòng)態(tài)決議
首先,還是回到lookUpImpOrForward主函數(shù)中
來看這個(gè)函數(shù)
// No implementation found. Try method resolver once.
//behavior = 3
//LOOKUP_RESOLVER = 2
//behavior & LOOKUP_RESOLVER = 3 & 2 = 0x11 & 0x10 = 0x10 = 2
if (slowpath(behavior & LOOKUP_RESOLVER)) {
//behavior & LOOKUP_RESOLVER = 1 & 2 = 0x01 & 0x10 = 0x00 = 0
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
behavior為0后,不在進(jìn)入此代碼,保證了每一個(gè)lookUpImpOrForward函數(shù)最多只能執(zhí)行一次resolveMethod_locked(動(dòng)態(tài)方法決議),直到behavior被重新賦值
tips:在底層經(jīng)??吹揭恍┻\(yùn)算符,補(bǔ)充幾個(gè)這次用到的:
&:按位與,參與運(yùn)算的兩數(shù)各對(duì)應(yīng)的二進(jìn)位相與。只有對(duì)應(yīng)的兩個(gè)二進(jìn)位都為1時(shí),結(jié)果位才為1。
^:按位異或,比較的是二進(jìn)制位,相同位置數(shù)字不同為1,相同為0
^=:按位異或,比較的是二進(jìn)制位,相同位置數(shù)字不同為1,相同為0,將結(jié)果賦值為運(yùn)算符左邊。
進(jìn)入resolveMethod_locked
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
// 給你一次機(jī)會(huì) 拯救地球 -> imp
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);
}
此函數(shù)里面有三個(gè)關(guān)鍵的函數(shù):
resolveInstanceMethod:實(shí)例方法動(dòng)態(tài)添加imp
resolveClassMethod:類方法動(dòng)態(tài)添加imp
lookUpImpOrForwardTryCache,當(dāng)完成添加之后,回到之前的慢速查找流程再來一遍
resolveInstanceMethod
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
//不會(huì)進(jìn)入return,因?yàn)橄到y(tǒng)有默認(rèn)實(shí)現(xiàn)resolveInstanceMethod
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
//系統(tǒng)會(huì)在此處為你發(fā)送一個(gè)消息resolve_sel
//當(dāng)你的這個(gè)類檢測(cè)了這個(gè)消息,并且做了處理
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
//那么此時(shí)系統(tǒng)會(huì)重新查找,此函數(shù)最終會(huì)觸發(fā)LookUpImpOrForward
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));
}
}
}
resolveClassMethod 類方法的動(dòng)態(tài)決議
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
//系統(tǒng)給resolveClassMethod函數(shù)默認(rèn)返回NO,即默認(rèn)實(shí)現(xiàn)了,不會(huì)return
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
//容錯(cuò)處理,對(duì)元類局部操作,防止沒有實(shí)現(xiàn)
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);
}
}
//系統(tǒng)會(huì)在此處為你發(fā)送一個(gè)消息resolve_sel
//當(dāng)你的這個(gè)類檢測(cè)了這個(gè)消息,并且做了處理
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
//類的類方法,就相當(dāng)于元類的對(duì)象方法
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));
}
}
}
lookUpImpOrForwardTryCache
通過動(dòng)態(tài)添加方法之后,再次嘗試查找sel對(duì)應(yīng)的最新添加的imp
IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior);
}
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
//當(dāng)類未初始化的時(shí)候,進(jìn)入lookUpImpOrForward
//在里面處理不緩存任何方法
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
//如果依然找不到,證明方法真的不存在,也就是說,只能依靠方法的動(dòng)態(tài)添加了
//那么此時(shí)再次進(jìn)入方法的慢速查找流程
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
//此判斷是當(dāng)前imp已經(jīng)存在了,并且這個(gè)imp是默認(rèn)賦值的forward_imp
//此時(shí)返回imp為nil;
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
整合實(shí)例對(duì)象/類對(duì)象的動(dòng)態(tài)方法決議
根據(jù)上述源碼分析,我們可以看到,只要我們實(shí)現(xiàn)了resolveInstanceMethod/resolveClassMethod這兩個(gè)方法,我們就可以攔截到崩潰前執(zhí)行的步驟,并做出一定改造的話,就可以攔截崩潰。
話不多說,上代碼。創(chuàng)建NSObject分類的方式將此實(shí)現(xiàn)都放在分類中:
@implementation NSObject (FF)
//為實(shí)例對(duì)象創(chuàng)建一個(gè)IMP
- (void)sayHelloCrash {
NSLog(@"%s",__func__);
}
//為元類對(duì)象創(chuàng)建創(chuàng)建一個(gè)IMP
+ (void)sayHelloBug {
NSLog(@"%@ - %s",self,__func__);
}
#pragma clang diagnostic push
// 讓編譯器忽略錯(cuò)誤
#pragma clang diagnostic ignored "-Wundeclared-selector"
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"人工介入resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
if (sel == @selector(say666)) {
IMP sayHelloCrashImp = class_getMethodImplementation(self, @selector(sayHelloCrash));
Method method = class_getInstanceMethod(self, @selector(sayHelloCrash));
const char * type = method_getTypeEncoding(method);
return class_addMethod(self, sel, sayHelloCrashImp, type);
} else if (sel == @selector(sayHappy)) {
IMP enjoyTimeImp = class_getMethodImplementation(objc_getMetaClass("HZMTeacher"), @selector(enjoyTime));
Method method = class_getInstanceMethod(objc_getMetaClass("HZMPerson"), @selector(enjoyTime));
const char * type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("HZMPerson"), sel, enjoyTimeImp, type);
}
return NO;
}
#pragma clang diagnostic pop
@end
為什么要寫在NSObject分類里呢?我們來分析下
其實(shí)通過方法慢速查找流程可以發(fā)現(xiàn)其查找路徑有兩條:
實(shí)例方法:類 -- 父類 -- 根類 -- nil
類方法:元類 -- 根元類 -- 根類 -- nil
它們的共同點(diǎn)是如果前面沒找到,都會(huì)來到根類即NSObject中查找,所以我們將上述的兩個(gè)方法統(tǒng)一整合在一起,以NSObject添加分類的方式來實(shí)現(xiàn)統(tǒng)一處理。而且由于類方法的查找,在其繼承鏈,查找的也是實(shí)例方法,所以可以將實(shí)例方法 和 類方法的統(tǒng)一處理放在resolveInstanceMethod
這種方式的實(shí)現(xiàn),正好與源碼中針對(duì)類方法的處理邏輯是一致的,即完美闡述為什么調(diào)用了類方法動(dòng)態(tài)方法決議,還要調(diào)用對(duì)象方法動(dòng)態(tài)方法決議(也就是resolveInstanceMethod為什么走了兩次的原因),其根本原因還是類方法在元類中的實(shí)例方法。
當(dāng)然,上面這種寫法還是會(huì)有其他的問題,比如系統(tǒng)方法也會(huì)被更改,針對(duì)這一點(diǎn),我們可以針對(duì)自定義類中方法統(tǒng)一方法名的前綴,根據(jù)前綴來判斷是否是自定義方法,然后統(tǒng)一處理自定義方法,例如可以在崩潰前pop到首頁(yè),再上報(bào)自己的服務(wù)器告知出問題的原因,主要是用于app線上防崩潰的處理,提升用戶的體驗(yàn)