iOS中方法的查找分為兩部分:
- 快速查找,在緩存中直接找到發(fā)送的消息
- 慢速查找,在緩存中如果沒找到就到了慢速查找,先找當前類,再找父類以及父類的父類,如果找到就執(zhí)行,如果找不到就執(zhí)行動態(tài)方法解析和消息轉發(fā)
1.代碼分析方法查找流程
三者關系如圖所示:

子類中對象方法的層層調用
LGStudent *student = [[LGStudent alloc] init];
// 對象方法
// 自己有 - 返回自己
[student sayHello];
// 自己沒有 - 老爸有 -
[student sayNB];
// 自己沒有 - 老爸沒有 - NSObject
[student sayMaster];
// 自己沒有 - 老爸沒有 - NSObject 沒有
// unrecognized selector sent to instance 0x103000450
[student performSelector:@selector(saySomething)];
打印結果:

結論:只要是繼承關系,子類可以調用父類中的所有對象方法,如果一直追究到NSObject中也沒有的對象方法saySomething,則會報出一個最經(jīng)典最常見的錯誤信息:unrecognized selector sent to instance 0x100562f70
子類中類方法的層層調用

打印結果:

結論:同上,只不過這里論證的是類方法。即,幾個類中不存在sayLove方法,所以會報經(jīng)典錯誤。
子類調用NSObject中的對象方法

結論:完全遵守isa的走位流程圖(http://www.itdecent.cn/p/cf18f3cc1306)
2.源碼分析消息查找流程
主要涉及方法:_class_lookupMethodAndLoadCache3

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)
參數(shù)解讀
- cls 當前類
- sel 當前方法的名字
- inst 當前類對象
- initialize 是否初始化
- cache 是否緩存
- resolver 是否在執(zhí)行動態(tài)方法解析
2.如果緩存命中 直接返回imp

if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
3.realizeClass 方法實現(xiàn)
static Class realizeClass(Class cls)
{
runtimeLock.assertLocked();
const class_ro_t *ro; // 方法列表 屬性列表 等的存儲結構題
class_rw_t *rw; // 存儲 class_ro_t的結構題
Class supercls; // 父類
Class metacls; // 元類
bool isMeta; // 當前類是否是元類
ro = (const class_ro_t *)cls->data(); // 給ro賦值
isMeta = ro->flags & RO_META; // 是否是元類判斷
supercls = realizeClass(remapClass(cls->superclass)); // 初始化父類
metacls = realizeClass(remapClass(cls->ISA())); // 初始化元類
cls->superclass = supercls; 找到當前類的 父類
cls->initClassIsa(metacls); 初始化當前類的元類
if (supercls) { // 如果有父類就把當前類關聯(lián)到父類的子類列表中
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
4.如果類沒有初始化就執(zhí)行初始化 _class_initialize 的代碼就不放了 主要作用就是 沒有初始化的類進行初始化 如果有父類并且父類也沒有初始化就連 父類也初始化掉 如果初始化完了就什么也不做 如果正在初始化 就等待
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
}
5.開始找方法和存儲緩存
{
Method meth = getMethodNoSuper_nolock(cls, sel); // 一個for循環(huán) 從方法列表中找方法
if (meth) { // 如果找到方法 開始緩存
log_and_fill_cache(cls, meth->imp, sel, inst, cls); // 這里會調用 cache_fill 上一篇已經(jīng)寫過
imp = meth->imp;
goto done; // 找到直接返回
}
}
6.如果當前類沒找到,就查找父類以及父類的父類,并且執(zhí)行緩存,如果找到直接返回
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
7.如果以上的過程都沒有找到方法,那么就執(zhí)行動態(tài)方法解析,也就是 if (resolver && !triedResolver) {這個判斷體中的內容}
8. _class_resolveMethod 方法實現(xiàn)
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
// 如果不是元類 說明是類 此時類中已經(jīng)沒有方法 直接執(zhí)行 _class_resolveInstanceMethod
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst); // 執(zhí)行 + (BOOL)resolveInstanceMethod:(SEL)sel方法
}
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); //
}
}
}
上述代碼當是元類的時候: cls 就是元類 inst 類對象 這時會做兩次嘗試 resolveClassMethod _class_resolveInstanceMethod
9.最后動態(tài)方法解析會走到 _class_resolveInstanceMethod, 如果類沒有實現(xiàn)resolveInstanceMethod就直接返回 如果類實現(xiàn)了 resolveInstanceMethod 就發(fā)送 SEL_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*/);
}