在之前寫(xiě)過(guò)一篇關(guān)于objc_msgSend
的流程分析,而它就是方法的快速查找调炬;那么本文將介紹一下方法的慢速查找流程函筋。
在objc_msgSend
的匯編代碼中盼忌,當(dāng)查找isa
完畢之后,他會(huì)跳轉(zhuǎn)到CacheLookup
的匯編代碼中執(zhí)行贝润,當(dāng)系統(tǒng)在緩存中沒(méi)有找到相應(yīng)的方法绊茧,他就會(huì)繼續(xù)跳轉(zhuǎn)到CheckMiss
的匯編代碼中。我們來(lái)看一下CheckMiss
的源碼:
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
當(dāng)系統(tǒng)走到這個(gè)過(guò)程中打掘,會(huì)繼續(xù)執(zhí)行NORMAL
中的cbz p9, __objc_msgSend_uncached
這一行代碼华畏,也就是說(shuō),我們就可以以這個(gè)方法為入口去探索慢速查找的過(guò)程尊蚁。
我們進(jìn)行全局搜索__objc_msgSend_uncached
這個(gè)亡笑,找到了MethodTableLookup
,這個(gè)是一個(gè)方法列表横朋,繼續(xù)搜索這個(gè)方法况芒,我們就只能查找到一個(gè)跳轉(zhuǎn)的bl _lookUpImpOrForward
,之后我們就無(wú)法從找到這個(gè)方法的內(nèi)容叶撒。
那么按照我們往常的套路,我們把下劃線去掉耐版,經(jīng)過(guò)搜索lookUpImpOrForward
這個(gè)方法祠够,找到了很多。
那么我們有沒(méi)有別的途徑去找到這個(gè)方法呢粪牲?
我們創(chuàng)建一個(gè)工程古瓤,在工程中創(chuàng)建一個(gè)類(lèi),在類(lèi)中創(chuàng)建一個(gè)方法,在調(diào)用方法前打上斷點(diǎn):
把這個(gè)模式打開(kāi),可可以看到匯編中我們打上斷點(diǎn)的方法處:
在方法下面的objc_msgSend
處打上斷點(diǎn):
就進(jìn)入了objc_msgSend
方法中去了:
繼續(xù)往下滑落君,就會(huì)看到下面圖的樣子穿香,在_objc_msgSend_uncached
處打上斷點(diǎn),繼續(xù)按住control+
接著就會(huì)跳轉(zhuǎn)到_objc_msgSend_uncached
方法中去绎速,就能看到lookUpImpOrForward at objc-runtime-new.mm:6099
這個(gè)信息皮获。
下面繼續(xù)描述一下方法的一些基本常識(shí):
在一個(gè)類(lèi)中,實(shí)例方法
存儲(chǔ)在類(lèi)
中纹冤,類(lèi)方法
存儲(chǔ)在元類(lèi)
中洒宝;
那么當(dāng)我們用調(diào)用類(lèi)方法
的方式去調(diào)用實(shí)例方法
,它也會(huì)執(zhí)行成功萌京;那為什么用類(lèi)方法的方式去調(diào)用實(shí)例方法雁歌,它也會(huì)成功呢?
答案在一張圖中:
在isa
的走位中知残,根元類(lèi)
最后也會(huì)走到NSObject
靠瞎,因此,我們可以用調(diào)用類(lèi)方法的方式去調(diào)用實(shí)例方法求妹。
下面繼續(xù)探索慢速查找流程:
由于在上面我們找到了lookUpImpOrForward
這個(gè)方法入口乏盐,那么我們就去看看這個(gè)方法的實(shí)現(xiàn)代碼:
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
runtimeLock.lock();
checkIsKnownClass(cls);
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
}
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
runtimeLock.assertLocked();
curClass = cls;
for (unsigned attempts = unreasonableClassCount();;) {
// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp;
goto done;
}
if (slowpath((curClass = curClass->superclass) == nil)) {
imp = forward_imp;
break;
}
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
break;
}
if (fastpath(imp)) {
goto done;
}
}
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
log_and_fill_cache(cls, imp, sel, inst, curClass);
runtimeLock.unlock();
done_nolock:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
在main函數(shù)中調(diào)用一個(gè)方法,在方法調(diào)用處打上斷點(diǎn)扒最,在執(zhí)行程序之后丑勤,在if (fastpath(behavior & LOOKUP_CACHE)) {
處打上斷點(diǎn),我們點(diǎn)擊下一步吧趣,程序就會(huì)跳轉(zhuǎn)到這一步上去法竞;
我們發(fā)現(xiàn),快速查找會(huì)查找緩存
中的方法强挫,而慢速查找岔霸,它還要查找cache_getImp
,那這是為什么呢?
答案是因?yàn)橐苊?code>多線程的影響俯渤,當(dāng)你程序在調(diào)用時(shí)呆细,線程可能偷偷摸摸的緩存了一次。
我們繼續(xù)往下看源碼八匠,在下面有兩個(gè)if slowpath
判斷絮爷,我們從上面的if (slowpath(!cls->isRealized()))
的判斷內(nèi)去看它的實(shí)現(xiàn):
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
它里面存在一個(gè)方法realizeClassMaybeSwiftAndLeaveLocked
,點(diǎn)擊方法進(jìn)去梨树,它返回的也是一個(gè)方法:realizeClassMaybeSwiftMaybeRelock
坑夯,繼續(xù)點(diǎn)擊,在realizeClassMaybeSwiftMaybeRelock
方法中抡四,有一個(gè)關(guān)鍵的方法:realizeClassWithoutSwift
,點(diǎn)擊進(jìn)去柜蜈,里面的代碼很多仗谆,貼出一部分源碼:
runtimeLock.assertLocked();
class_rw_t *rw;
Class supercls;
Class metacls;
if (!cls) return nil;
if (cls->isRealized()) return cls;
ASSERT(cls == remapClass(cls));
// fixme verify class is not in an un-dlopened part of the shared cache?
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = objc::zalloc<class_rw_t>();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
在這個(gè)方法中,首先讀cls
的data
數(shù)據(jù)淑履,對(duì)rw隶垮,ro進(jìn)行賦值,然后就有了數(shù)據(jù)秘噪;在有了數(shù)據(jù)之后狸吞,我們繼續(xù)讀下一塊代碼:
cls->chooseClassArrayIndex();
if (PrintConnecting) {
_objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
cls->nameForLogging(), isMeta ? " (meta)" : "",
(void*)cls, ro, cls->classArrayIndex(),
cls->isSwiftStable() ? "(swift)" : "",
cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
}
supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
里面有一個(gè)supercls
和metacls
兩個(gè)遞歸實(shí)現(xiàn),利用realizeClassWithoutSwift
缆娃,把整個(gè)繼承鏈遞歸下來(lái)捷绒,這符合那副經(jīng)典的走位圖。繼續(xù)看下一塊代碼:
// Update superclass and metaclass in case of remapping
cls->superclass = supercls;
cls->initClassIsa(metacls);
// Reconcile instance variable offsets / layout.
// This may reallocate class_ro_t, updating our ro variable.
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// Set fastInstanceSize if it wasn't set already.
cls->setInstanceSize(ro->instanceSize);
// Copy some flags from ro to rw
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
// Propagate the associated objects forbidden flag from ro or from
// the superclass.
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// Attach categories
methodizeClass(cls, previously);
return cls;
在這一塊中贯要,有對(duì)cls->superclass
和cls->initClassIsa
進(jìn)行賦值暖侨,然后在下面會(huì)對(duì)supercls
進(jìn)行判斷,分析這個(gè)if
判斷崇渗,它是一個(gè)雙向鏈表
結(jié)構(gòu),結(jié)下來(lái)的methodizeClass
是對(duì)所有列表和屬性都會(huì)添加到rwe
當(dāng)中去字逗。
看完realizeClassMaybeSwiftAndLeaveLocked
的大部分內(nèi)容之后,我們繼續(xù)往下看另一個(gè)slowpath
中的initializeAndLeaveLocked
方法;繼續(xù)點(diǎn)擊方法進(jìn)去宅广,最終走到initializeAndMaybeRelock
方法葫掉,在這個(gè)方法中最重要的一行代碼是調(diào)用的initializeNonMetaClass
這個(gè)方法,這個(gè)方法跟狱,貼出一塊代碼:
Class supercls;
bool reallyInitialize = NO;
initialize cls.
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs;
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
localWillInitializeFuncs.initFrom(willInitializeFuncs);
}
}
if (reallyInitialize) {
_setThisThreadIsInitializingClass(cls);
if (MultithreadedForkChild) {
// LOL JK we don't really call +initialize methods after fork().
performForkChildInitialize(cls, supercls);
return;
}
for (auto callback : localWillInitializeFuncs)
callback.f(callback.context, cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
objc_thread_self(), cls->nameForLogging());
}
上面的一塊代碼中俭厚,作用很明顯,層層遞歸
初始化所有方法的類(lèi)
;
知道這個(gè)作用之后驶臊,其他代碼也就不那么重要了挪挤,回到lookUpImpOrForward
方法,繼續(xù)往下看源碼关翎,它的返回值是imp
扛门,既然是返回imp,如果它找不到imp怎么辦呢纵寝?
那么我們就去看看imp賦值的地方在哪里论寨,他的重點(diǎn)在for (unsigned attempts = unreasonableClassCount();;)
for循環(huán)中,代碼在上面已經(jīng)貼出來(lái)了爽茴。我們?nèi)パ芯窟@一段代碼:
在代碼中有一個(gè)方法getMethodNoSuper_nolock
獲取cls
中的所有方法葬凳。
我們來(lái)看一下它的源碼:
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
// <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
// caller of search_method_list, inlining it turns
// getMethodNoSuper_nolock into a frame-less function and eliminates
// any store from this codepath.
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
可以看到cls->data()->methods()
,這個(gè)data()
就是之前加載過(guò)來(lái)的ro,rw這些東西室奏。
探索到這里沮明,就涉及到一個(gè)算法,所有的方法都在method_list中窍奋,那么系統(tǒng)如何去查找方法呢?
答案:二分查找,二分查以中間點(diǎn)為界限琳袄,將精確的值確定在某個(gè)范圍內(nèi)江场,經(jīng)過(guò)不斷縮小范圍,查找正確的內(nèi)容窖逗;
那么是如何知道系統(tǒng)是通過(guò)二分查找去找到對(duì)應(yīng)的方法呢址否?
在getMethodNoSuper_nolock
方法中有一個(gè)對(duì)method_t
類(lèi)型的*m賦值的search_method_list_inline
函數(shù),我們?cè)谶@個(gè)函數(shù)中有一個(gè)findMethodInSortedMethodList
方法碎紊,下面附上這個(gè)方法的源碼:
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
ASSERT(list);
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
在for循環(huán)遍歷所有方法中佑附,probe
的賦值中有一個(gè)count >> 1
位移操作,其作用是減少二分之一仗考;
例如:8的二進(jìn)制為1000音同; 8>>1 100 = 4;
通過(guò)上面的代碼秃嗜,我們可以了解到系統(tǒng)查找方法是使用二分查找
來(lái)尋找內(nèi)容权均。
我們可以在控制臺(tái)中打印list
中的內(nèi)容,就可以打印該類(lèi)中的所有方法。
那么在上面的代碼中锅锨,probe--又是怎么理解呢叽赊?
它其實(shí)是來(lái)判斷分類(lèi)重名的作用;
我們來(lái)探索一下:
首先創(chuàng)建一個(gè)Person必搞,里面創(chuàng)建一個(gè)方法必指,在對(duì)Person創(chuàng)建一個(gè)分類(lèi)。分類(lèi)中創(chuàng)建一個(gè)相同的方法恕洲,在main函數(shù)中調(diào)用塔橡,得到的結(jié)果是打印的是分類(lèi)方法。這邊就不貼代碼了研侣,實(shí)現(xiàn)起來(lái)比較簡(jiǎn)單谱邪。
當(dāng)系統(tǒng)沒(méi)有找到對(duì)應(yīng)的方法之后,它就會(huì)執(zhí)行goto done;
代碼:
done:
log_and_fill_cache(cls, imp, sel, inst, curClass);
runtimeLock.unlock();
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
cache_fill(cls, sel, imp, receiver);
}
那么在這個(gè)代碼中庶诡,它會(huì)對(duì)方法進(jìn)行緩存惦银,以方便下次快速查找。
那么整個(gè)流程就是:objc_msgSend -> 二分查找自己 -> cache_fill ->objc_msgSend
形成一個(gè)閉環(huán)末誓。
如果在自己中沒(méi)有找到方法扯俱,那么它就會(huì)去父類(lèi)去找,在父類(lèi)中快速查找沒(méi)找到喇澡,他就會(huì)執(zhí)行cache_getImp
方法迅栅,在經(jīng)過(guò)搜索這個(gè)方法,沒(méi)有找到晴玖,那么可以肯定的是读存,它存在匯編代碼中(只要與緩存相關(guān)的为流,一般都在匯編代碼中):
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
CacheLookup GETIMP, _cache_getImp
那么它又會(huì)執(zhí)行CacheLookup
方法,接著又會(huì)跑到lookUpImpOrForward
方法中查找让簿。
注意事項(xiàng):當(dāng)在父類(lèi)中快速查找中沒(méi)有找到方法敬察,那么它不會(huì)繼續(xù)遞歸進(jìn)行慢速查找,而是繼續(xù)找父類(lèi)的父類(lèi)(NSObject)的緩存中進(jìn)行快速查找尔当,當(dāng)在NSObject的緩存中沒(méi)有找到莲祸,那么繼續(xù)找NSObject的父類(lèi)nil的緩存,進(jìn)入一個(gè)死循環(huán)過(guò)程椭迎。
那為什么在父類(lèi)中沒(méi)有慢速查找過(guò)程呢锐帜?
由于imp的獲取在cache_getImp
中,通過(guò)全局搜索畜号,它存在匯編當(dāng)中缴阎,附上代碼:
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
CacheLookup GETIMP, _cache_getImp
可以看到它的CacheLookup
是GETIMP
類(lèi)型;因此弄兜,在CacheLookup
的匯編代碼最后會(huì)在JumpMiss
中药蜻,附上他的代碼:
.macro JumpMiss
.if $0 == GETIMP
b LGetImpMiss
.elseif $0 == NORMAL
b __objc_msgSend_uncached
.elseif $0 == LOOKUP
b __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
可以看到,只有當(dāng)CacheLookup
為NORMAL
時(shí)替饿,才會(huì)進(jìn)行慢速查找语泽。
而LGetImpMiss
的代碼中,它最后只是進(jìn)行return
返回视卢。
那么如果在父類(lèi)中還是沒(méi)有找到踱卵,那么在之前就會(huì)對(duì)(curClass = curClass->superclass) == nil)
會(huì)判斷,將imp
賦值為forward_imp
,之后會(huì)執(zhí)行這一句判斷imp == forward_imp
,相等的話(huà)据过,就會(huì)遞歸出去惋砂,跳出循環(huán)。
那么這個(gè)forward_imp
又是什么東西呢绳锅?
在上面的代碼中西饵,forward_imp是通過(guò)_objc_msgForward_impcache
方法進(jìn)行賦值的,我們?nèi)タ纯催@個(gè)方法鳞芙,經(jīng)過(guò)全局搜索眷柔,它存在匯編中,代碼我就不貼出來(lái)的原朝,直接寫(xiě)出關(guān)鍵方法__objc_forward_handler
,搜索_objc_forward_handler
驯嘱,就得到一個(gè)很有意思的東西:
__attribute__((noreturn, cold)) 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);
}
上面的代碼,相信很多iOS開(kāi)發(fā)者都見(jiàn)過(guò)喳坠。當(dāng)方法找不到時(shí)鞠评,就會(huì)顯示這個(gè)錯(cuò)誤。
那么在報(bào)錯(cuò)之前壕鹉,它還會(huì)走到下面這部分代碼中:
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
那么我們就去看看這部分代碼中的內(nèi)容剃幌,首先點(diǎn)擊resolveMethod_locked
聋涨,他是一個(gè)動(dòng)態(tài)方法決議;動(dòng)態(tài)方法決議其實(shí)是系統(tǒng)在沒(méi)有找到方法時(shí)负乡,它給你一個(gè)處理過(guò)程牛郑,處理完之后,它再繼續(xù)去查找敬鬓,只要能找到方法所需要的內(nèi)容,就不會(huì)報(bào)錯(cuò)笙各,如果沒(méi)有钉答,則報(bào)出錯(cuò)誤。
下面去看看它的源碼:
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 (!lookUpImpOrNil(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
在上面一段代碼中杈抢,重要的就在那個(gè)if判斷中数尿,我們?nèi)タ纯?code>resolveInstanceMethod方法,請(qǐng)看源碼:
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// 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 = lookUpImpOrNil(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));
}
}
}
這段源碼作用就是只要能找到這個(gè)方法的實(shí)現(xiàn)惶楼,那么它就會(huì)去執(zhí)行這個(gè)方法右蹦,動(dòng)態(tài)方法協(xié)議其實(shí)是蘋(píng)果給我們的一個(gè)機(jī)會(huì),在快速和慢速都沒(méi)找到相應(yīng)的方法歼捐,那么它就給你一個(gè)去實(shí)現(xiàn)這個(gè)方法的機(jī)會(huì)何陆,只要系統(tǒng)找到了這個(gè)方法,那么他就能執(zhí)行成功豹储。
下面我們用代碼來(lái)試一下:
在聲明文件中創(chuàng)建一個(gè)方法贷盲,在實(shí)現(xiàn)文件中不去寫(xiě)這個(gè)方法內(nèi)容,那么我們?cè)趍ain函數(shù)中執(zhí)行剥扣,它會(huì)報(bào)錯(cuò)巩剖,那么我們用另一種方式去寫(xiě),就不會(huì)報(bào)錯(cuò)了:
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(say666)) {
NSLog(@"%@ 來(lái)了",NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
上面的代碼是實(shí)例方法的動(dòng)態(tài)決議钠怯,那么類(lèi)方法的動(dòng)態(tài)決議該如何實(shí)現(xiàn)呢佳魔?
類(lèi)方法存在元類(lèi)中,那么我們?cè)谔砑訉?duì)象時(shí)晦炊,需要將類(lèi)對(duì)象替換成元類(lèi)對(duì)象鞠鲜,而元類(lèi)一層一層往上走到最后,它最后走到的也是NSObject中刽锤,因此镊尺,我們可以在實(shí)例方法中利用if
去進(jìn)行判斷,將類(lèi)方法的動(dòng)態(tài)決議也寫(xiě)在實(shí)例方法中并思,請(qǐng)看代碼:
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@ 來(lái)了",NSStringFromSelector(sel));
if (sel == @selector(say666)) {
NSLog(@"%@ 來(lái)了",NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(self, sel, imp, type);
}
else if (sel == @selector(sayNB)) {
IMP imp = class_getMethodImplementation(objc_getMetaClass("Person"), @selector(lgClassMethod));
Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("Person"), @selector(lgClassMethod));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
}
return NO;
}
在上面的代碼中庐氮,say666
是實(shí)例方法,sayNB
是類(lèi)方法宋彼,在代碼中弄砍,區(qū)別很明顯仙畦,類(lèi)方法的對(duì)象是是要獲得該類(lèi)的元類(lèi)。
上面的代碼就是我們對(duì)中間層的處理音婶,當(dāng)系統(tǒng)沒(méi)有在類(lèi)中找到這個(gè)方法慨畸,那么就會(huì)啟動(dòng)動(dòng)態(tài)決議,給我們一個(gè)處理的過(guò)程衣式,處理完之后寸士,再重新去執(zhí)行lookUpImpOrForward
,這樣就不會(huì)報(bào)錯(cuò)了碴卧。
重點(diǎn):lookUpImpOrForward
這個(gè)方法會(huì)執(zhí)行兩次弱卡,第一次是動(dòng)態(tài)方法解析,第二次是慢速轉(zhuǎn)發(fā)過(guò)程住册。