上一篇 從彙編探索objc_msgSend 遺留了一個 __class_lookupMethodAndLoadCache3
,接下來就是對這個方法進行分析,也就會來到了消息查找流程,消息查找流程分爲快速和慢速,快速查找已經在 objc_msgSend
找過了,找不到就會進入慢速查找,慢速查找流程會在 __class_lookupMethodAndLoadCache3
方法進行,可是我先不直接分析,先來點鋪墊,想看源碼分析的能夠跳過第二步。緩存
咱們先建立 Student
和 Person
兩個對象,Student
-> Person
-> NSObject
(->表明繼承),而後在兩個對象以及 NSObject
分類中各自添加一個實例方法和類方法。bash
#pragma clang diagnostic push
// 讓編譯器忽略錯誤
#pragma clang diagnostic ignored "-Wundeclared-selector"
Student *student = [[Student alloc] init];
// 對象方法測試
// 1.對象的實例方法 - 本身有
[student studentSayHello];
// 2.對象的實例方法 - 本身沒有 - 找老爸的
[student personSayNB];
// 3.對象的實例方法 - 本身沒有 - 老爸沒有 - 找老爸的老爸 - NSObject
[student nsobjectSayMaster];
// 4.對象的實例方法 - 本身沒有 - 老爸沒有 - 找老爸的老爸 -> NSObject 也沒有 - 奔潰
// [student performSelector:@selector(saySomething)];
// 類方法測試
// 5.類方法 - 本身有
[Student studentSayObjc];
// 6.類方法 - 本身沒有 - 老爸有
[Student personSayHappay];
// 7.類方法 - 本身沒有 - 找老爸的老爸 - NSObject
[Student nsobjectSayEasy];
// 8.類方法- 本身沒有 - 老爸沒有 - 找老爸的老爸 -> NSObject 也沒有 - 奔潰
// [LGStudent performSelector:@selector(sayLove)];
// 9.類方法- 本身沒有 - 老爸沒有 - 找老爸的老爸 -> NSObject 也沒有 - 可是有對象方法,能走不會奔潰
[Student nsobjectSayEasy];
#pragma clang diagnostic pop
複製代碼
上面幾個方法的調用結果我已經寫到方法上面了,可是第9點,爲何類調用 NSObject
的對象方法可以成功,這就須要一個很牛皮的圖了,isa走位圖
,下面奉上,因爲類方法會保存在元類的對象方法列表裏,因此類調用類方法時,會去元類的對象方法列表裏面找,沒找到再去根元類裏找,根元類的父類是 NSObject
,因此會找到 NSObject
裏,因此 Student
最終會在 NSObject
對象方法列表裏找到 nsobjectSayEasy
進行調用。 多線程
當彙編的 objc_msgSend
在緩存沒有找到方法時,最終會走到這個方法進行慢速查找方法。app
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
/*
cls:若是是實例方法那就是類,若是是類方法那就是元類
sel:方法名
obj:方法調用者
*/
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
複製代碼
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// 根據外界傳來的值進行判斷,這裏爲 NO,不走
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// 防止多線程訪問兩個方法出現返回imp錯誤
runtimeLock.lock();
// 非法類的檢查
checkIsKnownClass(cls);
if (!cls->isRealized()) {
// 準備條件,若是當前這個類沒有被加載,須要對當前類進行初始化以及屬性和方法的加載
realizeClass(cls);
}
// 沒有當前類沒有初始化就進行初始化操做
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won’t happen. 2778172
}
retry:
runtimeLock.assertLocked();
// 類初始化後再對緩存進行一次查找,我的認爲蘋果爸爸仍是很嚴謹的
imp = cache_getImp(cls, sel);
if (imp) goto done;
{
// 在當前類中找方法,裏面會經過二分查找節省時間,有興趣的能夠本身去看看,在 search_method_list 這個方法裏面
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
// 找到了進行緩存一份
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
{
// 當本類找不到的時候,就會去父類緩存查找,若是沒有在遞歸查找
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// 看看父類的緩存裏面是否存在該方法
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// 找到後進行緩存
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
break;
}
}
// 沒有緩存就去父類的方法列表慢速查找
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// 若是本類和父類以及父父類都沒找到,就會進行動態方法決議
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
// 此處代碼只會走一次,由於 triedResolver 會設置爲 YES
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn’t help.
// 若是本類及父類都沒有找到,動態方法決議也沒有幫助,就會調用 _objc_msgForward_impcache 方法
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}
複製代碼
_class_resolveMethod
動態方法決議(動態方法決議下篇博客會分析到),若是上述都沒有幫助,則會調用 _objc_msgForward_impcache
方法,這個方法又是作什麼的呢?立刻介紹。經過全局搜索,一步步查找來到 objc-msg-arm64.s
彙編文件中找到下面彙編代碼,以後會調用 __objc_forward_handler
。函數
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
複製代碼
因爲這個函數是 C
函數,咱們去掉前面一個 _
進行全局搜索_objc_forward_handler
,就能找到沒有實現方法所形成的崩潰緣由了,哈哈哈哈,是否是很熟悉,unrecognized selector sent to instance
這個崩潰緣由常常遇到,終於找到底層源碼在哪了。源碼分析
__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;
複製代碼
objc_msgSend
緩存中沒有找到方法,就會來到 class_lookupMethodAndLoadCache3
進行慢速查找流程。lookUpImpOrForward
裏面會先去本類當中查找方法 getMethodNoSuper_nolock
,本類沒有找到就會去遞歸的去父類當中查找。_class_resolveMethod
,這是蘋果爸爸給咱們的最後一次機會。_objc_forward_handler
,而後崩潰報錯 selector sent to instance
。