Runtime底层原理--动态方法解析总结
方法的底層會編譯成消息,消息進行遞歸,先從實例方法開始查找,到父類最后到NSObject。如果在匯編部分快速查找沒有找到IMP,就會進入C/C++中的動態方法解析進入lookUpImpOrForward方法進行遞歸。
動態方法解析
動態方法解析分為實例方法和類方法兩種。
實例方法查找imp流程和動態方法解析
比如執行一個Student實例方法eat,會先去這個類中查找是否有該方法(sel),如果有則進行存儲以便下次直接從匯編部分快速查找。
// 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)的緩存和方法列表找查找。如果在父類中找到先緩存再執行done.
// 元類的父類 - NSObject 是否有 實例方法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;}如果最終還是沒找到,則會進入動態方法解析_class_resolveMethod,先判斷當前cls對象是不是元類,也就是如果是對象方法會走到_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);}} }如果元類,那么執行_class_resolveInstanceMethod(cls, sel, inst)方法,該方法會執行lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, NO/*initialize*/, YES/*cache*/, NO/*resolver*/),查找當前的cls的isa是否實現了resolveInstanceMethod,也就是是否有自定義實現、是否重寫了。如果查到了就會給類對象發送消息objc_msgSend,調起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的內部是通過lookUpImpOrForward方法進行查找,再次回到遞歸調用。
如果還是沒查到,這里就不會再次進入動態方法解析(注:如果再次進入動態方法解析會形成死遞歸),首先對cls的元類進行查找,然后元類的父類,也就是根元類(系統默認實現的虛擬的)進行查找、最終到NSObjece,只不過NSObjece中默認實現resolveInstanceMethod方法返回NO,也就是此時在元類進行查找的時候找到了resolveInstanceMethod方法,并停止繼續查找,這就是為什么動態方法解析后的遞歸沒有再次進入動態方法解析的原因。如果最終還是沒有找到SEL_resolveInstanceMethod則說明程序有問題,直接返回。下面是isa走位圖:
如果找到的imp不是轉發的imp,則返回imp。
舉個例子:
在Student中有個對象run方法,但是并沒有實現,當調用run方法時,最終沒有找到imp會崩潰。通過動態方法解析,實現run方法
此時只是給對象方法添加了一個imp,接下來再次進入查找imp流程,重復之前的操作,只不過現在對象方法已經有了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. clsIMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); // ...省略N行代碼動態方法解析的實質: 經過漫長的查找并沒有找到sel的imp,系統會發送resolveInstanceMethod消息,為了防止系統崩潰,可以在該方法內對sel添加imp,系統會自動再次查找imp。
類方法查找imp流程和動態方法解析
類方法查找imp流程和實例方法查找imp前面流程一樣,也是從匯編部分快速查找,之后判斷cls是不是元類,在元類方法列表中查找,如果元類中沒有當前的sel,就去元類的父類中查找,還沒有就去根元類的父類NSObject中查找,此時查找的就是NSObject中是否有這個實例對象方法,如果NSObject中也沒有就會進入動態方法解析_class_resolveMethod。類對象這里的cls和對象方法不一樣,因為cls是元類所以直接走_class_resolveClassMethod方法。進入_class_resolveClassMethod方法還是先判斷resolveClassMethod方法是否有實現,之后發送消息objc_msgSend,這里和實例方法有所區別,類方法會執行_class_getNonMetaClass方法,內部實現getNonMetaClass,getNonMetaClass會判斷當前cls是不是NSObject,判斷當前的cls是不是根元類,也就是自己,接下來判斷inst類對象,判斷inst類對象的isa如果不是元類,那么返回類對象的父類,不是就返回類對象。在_class_resolveClassMethod方法中添加了imp后還是和實例方法一樣,再次進入重新查找流程,此時如果還是沒有,那么類方法還會再一次的進入_class_resolveInstanceMethod方法,和實例方法不同的是resolveInstanceMethod方法內部的cls是元類,所以找的方法也就是- (BOOL)resolveClassMethod:(SEL)sel,可以在NSObject中添加+ (BOOL)resolveClassMethod:(SEL)sel方法,這樣無論類方法還是實例方法都會走到這里,可以作為__防崩潰的處理__。
/*********************************************************************** * 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 classif (!metacls->isMetaClass()) return metacls;// metacls really is a metaclass// special case for root metaclass// where inst == inst->ISA() == metacls is possibleif (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 availableif (inst) {Class cls = (Class)inst;realizeClass(cls);// cls may be a subclass - find the real class for metaclswhile (cls && cls->ISA() != metacls) {cls = cls->superclass;realizeClass(cls);}if (cls) {assert(!cls->isMetaClass());assert(cls->ISA() == metacls);return cls;}我們在Student類中添加未實現的類方法walk,在NSObject類中添加一個對象方法walk,運行程序不會崩潰。類方法先遞歸,開始找父類,最終在NSObject類中好到對象方法walk。
TIP:對象方法存儲在類中,類方法存儲在元類里面,類對象以實例方法的形式存儲在元類中。可以通過輸出class_getInstanceMethod方法和class_getClassMethod方法的imp指針來驗證,當然源碼也可以解釋在cls的元類中查找實例方法
/*********************************************************************** * 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進行驗證,動態方法解析的時候執行lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)方法,這里的cls就是inst的元類
# define ISA_MASK 0x00007ffffffffff8ULL// ------------------------------------------------- #if SUPPORT_NONPOINTER_ISAinline Class objc_object::ISA() {assert(!isTaggedPointer()); #if SUPPORT_INDEXED_ISAif (isa.nonpointer) {uintptr_t slot = isa.indexcls;return classForIndex((unsigned)slot);}return (Class)isa.bits; #elsereturn (Class)(isa.bits & ISA_MASK); #endif }這里看到初始化的時候isa.bits & ISA_MASK,我們先后打印cls和inst的信息,也可以驗證當前指針指向當前的元類。
動態方法解析作用
適用于重定向,也可以做防崩潰處理,也可以做一些錯誤日志收集等等。動態方法解析本質就是提供機會(任何沒有實現的方法都可以重新實現)。
該文章為記錄本人的學習路程,希望能夠幫助大家,也歡迎大家點贊留言交流!!!文章地址:https://www.jianshu.com/p/a7db9f0c82d6
總結
以上是生活随笔為你收集整理的Runtime底层原理--动态方法解析总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Xcode无线调试
- 下一篇: Runtime底层原理总结--反汇编分析