方法的底層會(huì)編譯成消息,消息進(jìn)行遞歸,先從實(shí)例方法開始查找腹纳,到父類最后到NSObject。如果在匯編部分快速查找沒有找到IMP驱犹,就會(huì)進(jìn)入C/C++中的動(dòng)態(tài)方法解析進(jìn)入
lookUpImpOrForward
方法進(jìn)行遞歸嘲恍。
動(dòng)態(tài)方法解析
動(dòng)態(tài)方法解析分為實(shí)例方法和類方法兩種。
實(shí)例方法查找imp流程和動(dòng)態(tài)方法解析
比如執(zhí)行一個(gè)Student實(shí)例方法eat着绷,會(huì)先去這個(gè)類中查找是否有該方法(sel),如果有則進(jìn)行存儲(chǔ)以便下次直接從匯編部分快速查找蛔钙。
// Try this class's cache.
// Student元類 - 父類 (根元類) -- NSObject
// resovleInstance 防止遞歸 --
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
如果沒有sel那么接下來去父類(直到NSObject)的緩存和方法列表找查找。如果在父類中找到先緩存再執(zhí)行done.
// 元類的父類 - NSObject 是否有 實(shí)例方法
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.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
如果最終還是沒找到荠医,則會(huì)進(jìn)入動(dòng)態(tài)方法解析_class_resolveMethod
吁脱,先判斷當(dāng)前cls對象是不是元類,也就是如果是對象方法會(huì)走到_class_resolveInstanceMethod
方法彬向,
/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
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);
}
}
}
如果元類兼贡,那么執(zhí)行_class_resolveInstanceMethod(cls, sel, inst)
方法,該方法會(huì)執(zhí)行lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)
娃胆,查找當(dāng)前的cls的isa
是否實(shí)現(xiàn)了resolveInstanceMethod
遍希,也就是是否有自定義實(shí)現(xiàn)、是否重寫了里烦。如果查到了就會(huì)給類對象發(fā)送消息objc_msgSend
凿蒜,調(diào)起resolveInstanceMethod
方法
/***********************************************************************
* lookUpImpOrNil.
* Like lookUpImpOrForward, but returns nil instead of _objc_msgForward_impcache
**********************************************************************/
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
lookUpImpOrNil
的內(nèi)部是通過lookUpImpOrForward
方法進(jìn)行查找禁谦,再次回到遞歸調(diào)用。
如果還是沒查到废封,這里就不會(huì)再次進(jìn)入動(dòng)態(tài)方法解析(注:如果再次進(jìn)入動(dòng)態(tài)方法解析會(huì)形成死遞歸)州泊,首先對cls的元類進(jìn)行查找,然后元類的父類漂洋,也就是根元類(系統(tǒng)默認(rèn)實(shí)現(xiàn)的虛擬的)進(jìn)行查找遥皂、最終到NSObjece,只不過NSObjece中默認(rèn)實(shí)現(xiàn)resolveInstanceMethod
方法返回NO刽漂,也就是此時(shí)在元類進(jìn)行查找的時(shí)候找到了resolveInstanceMethod
方法演训,并停止繼續(xù)查找,這就是為什么動(dòng)態(tài)方法解析后的遞歸沒有再次進(jìn)入動(dòng)態(tài)方法解析的原因贝咙。如果最終還是沒有找到SEL_resolveInstanceMethod
則說明程序有問題样悟,直接返回。下面是isa走位圖:
如果找到的imp不是轉(zhuǎn)發(fā)的imp颈畸,則返回imp乌奇。
舉個(gè)例子:
在Student中有個(gè)對象run方法,但是并沒有實(shí)現(xiàn)眯娱,當(dāng)調(diào)用run方法時(shí)礁苗,最終沒有找到imp會(huì)崩潰。通過動(dòng)態(tài)方法解析徙缴,實(shí)現(xiàn)run方法
#pragma mark - 動(dòng)態(tài)方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"動(dòng)態(tài)方法解析 - %@",self);
if (sel == @selector(run)) {
// 我們動(dòng)態(tài)解析對象方法
NSLog(@"對象方法 run 解析走這里");
SEL readSEL = @selector(readBook);
Method readM= class_getInstanceMethod(self, readSEL);
IMP readImp = method_getImplementation(readM);
const char *type = method_getTypeEncoding(readM);
return class_addMethod(self, sel, readImp, type);
}
return [super resolveInstanceMethod:sel];
}
此時(shí)只是給對象方法添加了一個(gè)imp试伙,接下來再次進(jìn)入查找imp流程,重復(fù)之前的操作于样,只不過現(xiàn)在對象方法已經(jīng)有了imp疏叨。
/***********************************************************************
* _class_resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
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*/);
// ...省略N行代碼
動(dòng)態(tài)方法解析的實(shí)質(zhì): 經(jīng)過漫長的查找并沒有找到sel的imp,系統(tǒng)會(huì)發(fā)送resolveInstanceMethod
消息穿剖,為了防止系統(tǒng)崩潰蚤蔓,可以在該方法內(nèi)對sel添加imp,系統(tǒng)會(huì)自動(dòng)再次查找imp糊余。
類方法查找imp流程和動(dòng)態(tài)方法解析
類方法查找imp流程和實(shí)例方法查找imp前面流程一樣秀又,也是從匯編部分快速查找,之后判斷cls是不是元類贬芥,在元類方法列表中查找吐辙,如果元類中沒有當(dāng)前的sel,就去元類的父類中查找蘸劈,還沒有就去根元類的父類NSObject中查找昏苏,此時(shí)查找的就是NSObject中是否有這個(gè)實(shí)例對象方法,如果NSObject中也沒有就會(huì)進(jìn)入動(dòng)態(tài)方法解析_class_resolveMethod
。類對象這里的cls和對象方法不一樣贤惯,因?yàn)閏ls是元類所以直接走_class_resolveClassMethod
方法洼专。進(jìn)入_class_resolveClassMethod
方法還是先判斷resolveClassMethod
方法是否有實(shí)現(xiàn),之后發(fā)送消息objc_msgSend
救巷,這里和實(shí)例方法有所區(qū)別壶熏,類方法會(huì)執(zhí)行_class_getNonMetaClass
方法句柠,內(nèi)部實(shí)現(xiàn)getNonMetaClass
浦译,getNonMetaClass
會(huì)判斷當(dāng)前cls是不是NSObject,判斷當(dāng)前的cls是不是根元類溯职,也就是自己精盅,接下來判斷inst類對象,判斷inst類對象的isa如果不是元類谜酒,那么返回類對象的父類叹俏,不是就返回類對象。在_class_resolveClassMethod
方法中添加了imp后還是和實(shí)例方法一樣僻族,再次進(jìn)入重新查找流程粘驰,此時(shí)如果還是沒有,那么類方法還會(huì)再一次的進(jìn)入_class_resolveInstanceMethod
方法述么,和實(shí)例方法不同的是resolveInstanceMethod
方法內(nèi)部的cls是元類蝌数,所以找的方法也就是- (BOOL)resolveClassMethod:(SEL)sel
,可以在NSObject中添加+ (BOOL)resolveClassMethod:(SEL)sel
方法度秘,這樣無論類方法還是實(shí)例方法都會(huì)走到這里顶伞,可以作為防崩潰的處理。
/***********************************************************************
* getNonMetaClass
* Return the ordinary class for this class or metaclass.
* `inst` is an instance of `cls` or a subclass thereof, or nil.
* Non-nil inst is faster.
* Used by +initialize.
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static Class getNonMetaClass(Class metacls, id inst)
{
static int total, named, secondary, sharedcache;
runtimeLock.assertLocked();
realizeClass(metacls);
total++;
// return cls itself if it's already a non-meta class
if (!metacls->isMetaClass()) return metacls;
// metacls really is a metaclass
// special case for root metaclass
// where inst == inst->ISA() == metacls is possible
if (metacls->ISA() == metacls) {
Class cls = metacls->superclass;
assert(cls->isRealized());
assert(!cls->isMetaClass());
assert(cls->ISA() == metacls);
if (cls->ISA() == metacls) return cls;
}
// use inst if available
if (inst) {
Class cls = (Class)inst;
realizeClass(cls);
// cls may be a subclass - find the real class for metacls
while (cls && cls->ISA() != metacls) {
cls = cls->superclass;
realizeClass(cls);
}
if (cls) {
assert(!cls->isMetaClass());
assert(cls->ISA() == metacls);
return cls;
}
我們在Student類中添加未實(shí)現(xiàn)的類方法walk
剑梳,在NSObject類中添加一個(gè)對象方法walk
唆貌,運(yùn)行程序不會(huì)崩潰。類方法先遞歸垢乙,開始找父類锨咙,最終在NSObject類中好到對象方法walk
。
TIP:對象方法存儲(chǔ)在類中追逮,類方法存儲(chǔ)在元類里面酪刀,類對象以實(shí)例方法的形式存儲(chǔ)在元類中⊙蛞迹可以通過輸出class_getInstanceMethod
方法和class_getClassMethod
方法的imp指針來驗(yàn)證蓖宦,當(dāng)然源碼也可以解釋在cls的元類中查找實(shí)例方法
/***********************************************************************
* class_getClassMethod. Return the class method for the specified
* class and selector.
**********************************************************************/
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
還可以通過LLDB進(jìn)行驗(yàn)證,動(dòng)態(tài)方法解析的時(shí)候執(zhí)行lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)
方法油猫,這里的cls就是inst的元類
# define ISA_MASK 0x00007ffffffffff8ULL
// -------------------------------------------------
#if SUPPORT_NONPOINTER_ISA
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
這里看到初始化的時(shí)候isa.bits & ISA_MASK
稠茂,我們先后打印cls和inst的信息,也可以驗(yàn)證當(dāng)前指針指向當(dāng)前的元類。
動(dòng)態(tài)方法解析作用
適用于重定向睬关,也可以做防崩潰處理诱担,也可以做一些錯(cuò)誤日志收集等等。動(dòng)態(tài)方法解析本質(zhì)就是提供機(jī)會(huì)(任何沒有實(shí)現(xiàn)的方法都可以重新實(shí)現(xiàn))电爹。
該文章為記錄本人的學(xué)習(xí)路程蔫仙,希望能夠幫助大家,也歡迎大家點(diǎn)贊留言交流Xぢ帷R“睢!文章地址:http://www.reibang.com/p/a7db9f0c82d6