Objective-C runtime機制(2)——消息機制

原文地址

當(dāng)我們用中括號[]調(diào)用OC函數(shù)的時候尼夺,實際上會進入消息發(fā)送和消息轉(zhuǎn)發(fā)流程.

消息發(fā)送(Messaging),runtime系統(tǒng)會根據(jù)SEL查找對應(yīng)的IMP炒瘸,查找到淤堵,則調(diào)用函數(shù)指針進行方法調(diào)用;若查找不到顷扩,則進入動態(tài)消息解析和轉(zhuǎn)發(fā)流程拐邪,如果動態(tài)解析和消息轉(zhuǎn)發(fā)失敗,則程序crash并記錄日志隘截。


消息相關(guān)數(shù)據(jù)結(jié)構(gòu)

SEL

SEL被稱之為消息選擇器扎阶,它相當(dāng)于一個key,在類的消息列表中婶芭,可以根據(jù)這個key东臀,來查找到對應(yīng)的消息實現(xiàn)。
在runtime中犀农,SEL的定義是這樣的:

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

它是一個不透明的定義惰赋,似乎蘋果故意隱藏了它的實現(xiàn)。目前SEL僅是一個字符串呵哨。

里要注意赁濒,即使消息的參數(shù)類型不同(注意,不是指參數(shù)數(shù)量不同)或方法所屬的類也不同孟害,但只要方法名相同拒炎,SEL也是一樣的。所以挨务,SEL單獨并不能作為唯一的Key击你,必須結(jié)合消息發(fā)送的目標Class玉组,才能找到最終的IMP。

我們可以通過OC編譯器命令@selector()或runtime函數(shù)sel_registerName丁侄,來獲取一個SEL類型的方法選擇器球切。

method_t

當(dāng)需要發(fā)送消息的時候,runtime會在Class的方法列表中尋找方法的實現(xiàn)绒障。在方法列表中方法是以結(jié)構(gòu)體method_t存儲的吨凑。

struct method_t {
    SEL name;
    const char *types;
    IMP imp;

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

可以看到method_t包含一個SEL作為key,同時有一個指向函數(shù)實現(xiàn)的指針I(yè)MP户辱。method_t還包含一個屬性const char *types;
types是一個C字符串鸵钝,用于表明方法的返回值和參數(shù)類型。一般是這種格式的:

v24@0:8@16

關(guān)于SEL type庐镐,可以參考 Type Encodings

IMP

IMP實際是一個函數(shù)指針恩商,用于實際的方法調(diào)用。在runtime中定義是這樣的:

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

IMP是由編譯器生成的必逆,如果我們知道了IMP的地址怠堪,則可以繞過runtime消息發(fā)送的過程,直接調(diào)用函數(shù)實現(xiàn)名眉。關(guān)于這一點粟矿,我們稍后會談到。

在消息發(fā)送的過程中损拢,runtime就是根據(jù)id和SEL來唯一確定IMP并調(diào)用之的陌粹。


消息

當(dāng)我們用[]向OC對象發(fā)送消息時,編譯器會對應(yīng)的代碼修改為objc_msgSend, 其定義如下:

OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

其實福压,除了objc_msgSend掏秩,編譯器還會根據(jù)實際情況,將消息發(fā)送改寫為下面四個msgSend之一:

objc_msgSend
objc_msgSend_stret

objc_msgSendSuper
objc_msgSendSuper_stret

當(dāng)我們將消息發(fā)送給super class的時候荆姆,編譯器會將消息發(fā)送改寫為**SendSuper的格式蒙幻,如調(diào)用[super viewDidLoad],會被編譯器改寫為objc_msgSendSuper的形式胆筒。

objc_msgSendSuper 的定義如下:

OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

可以看到邮破,調(diào)用super方法時,msgSendSuper的第一個參數(shù)不是id self腐泻,而是一個objc_super * 决乎。objc_super定義如下:

struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;
    /// Specifies the particular superclass of the instance to message. 
    __unsafe_unretained _Nonnull Class super_class;

};

objc_super包含兩個數(shù)據(jù)队询,receiver指調(diào)用super方法的對象派桩,即子類對象,而super_class表示子類的Super Class蚌斩。

這就說明了在消息過程中調(diào)用了 super方法和沒有調(diào)用super方法铆惑,還是略有差異的。我們將會在下面講解。

至于**msgSend中以_stret結(jié)尾的员魏,表明方法返回值是一個結(jié)構(gòu)體類型丑蛤。

在objc_msgSend的內(nèi)部,會依次執(zhí)行:

  • 檢測selector是否是應(yīng)該忽略的撕阎,比如在Mac OS X開發(fā)中受裹,有了垃圾回收機制,就不會響應(yīng)retain虏束,release這些函數(shù)棉饶。
  • 判斷當(dāng)前receiver是否為nil,若為nil镇匀,則不做任何響應(yīng)照藻,即向nil發(fā)送消息,系統(tǒng)不會crash汗侵。
  • 檢查Class的method cache幸缕,若cache未命中,則進而查找Class 的 method list晰韵。
  • 若在Class 的method list中未找到對應(yīng)的IMP发乔,則進行消息轉(zhuǎn)發(fā)
  • 若消息轉(zhuǎn)發(fā)失敗,程序crash

objc_msgSend

objc_msgSend 的偽代碼實現(xiàn)如下:

id objc_msgSend(id self, SEL cmd, ...) {
    if(self == nil)
        return 0;
    Class cls = objc_getClass(self);
    IMP imp = class_getMethodImplementation(cls, cmd);
    return imp?imp(self, cmd, ...):0;
}

而在runtime源碼中雪猪,objc_msgSend方法其實是用匯編寫的列疗。為什么用匯編?一是因為objc_msgSend的返回值類型是可變的浪蹂,需要用到匯編的特性抵栈;二是因為匯編可以提高代碼的效率。

對應(yīng)arm64坤次,其匯編源碼是這樣的(有所刪減):

    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    MESSENGER_START

    cmp x0, #0          // nil check and tagged pointer check
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
    ldr x13, [x0]       // x13 = isa
    and x16, x13, #ISA_MASK // x16 = class  
LGetIsaDone:
    CacheLookup NORMAL      // calls imp or objc_msgSend_uncached

LNilOrTagged:
    b.eq    LReturnZero     // nil check
END_ENTRY _objc_msgSend

雖然不懂匯編古劲,但是結(jié)合注釋,還是能夠猜大體意思的缰猴。

首先产艾,系統(tǒng)通過cmp x0, #0檢測receiver是否為nil。如果為nil滑绒,則進入LNilOrTagged闷堡,返回0;

如果不為nil疑故,則現(xiàn)將receiver的isa存入x13寄存器杠览;

在x13寄存器中,取出isa中的class纵势,放到x16寄存器中踱阿;

調(diào)用CacheLookup NORMAL管钳,在這個函數(shù)中,首先查找class的cache软舌,如果未命中才漆,則進入objc_msgSend_uncached。

objc_msgSend_uncached 也是匯編佛点,實現(xiàn)如下:

STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band x16 is the class to search
    
    MethodTableLookup
    br  x17

    END_ENTRY __objc_msgSend_uncached

其內(nèi)部調(diào)用了MethodTableLookup醇滥, MethodTableLookup是一個匯編的宏定義,其內(nèi)部會調(diào)用C語言函數(shù)_class_lookupMethodAndLoadCache3:

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

最終超营,會調(diào)用到lookUpImpOrForward來尋找class的IMP實現(xiàn)或進行消息轉(zhuǎn)發(fā)腺办。

lookUpImpOrForward

lookUpImpOrForward方法的目的在于根據(jù)class和SEL,在class或其super class中找到并返回對應(yīng)的實現(xiàn)IMP糟描,同時怀喉,cache所找到的IMP到當(dāng)前class中。如果沒有找到對應(yīng)IMP船响,lookUpImpOrForward會進入消息轉(zhuǎn)發(fā)流程躬拢。

lookUpImpOrForward 的簡化版實現(xiàn)如下:

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // 先在class的cache中查找imp
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }
     
   runtimeLock.read();

    if (!cls->isRealized()) {
        runtimeLock.unlockRead();
        runtimeLock.write();
        // 如果class沒有被relize,先relize
        realizeClass(cls);

        runtimeLock.unlockWrite();
        runtimeLock.read();
    }

    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        // 如果class沒有init见间,則先init
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
    }

    
 retry:    
    runtimeLock.assertReading();

    // relaized并init了class聊闯,再試一把cache中是否有imp
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // 先在當(dāng)前class的method list中查找有無imp
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // 在當(dāng)前class中沒有找到imp,則依次向上查找super class的方法列表
    {
        unsigned attempts = unreasonableClassCount();
        // 進入for循環(huán)米诉,沿著繼承鏈菱蔬,依次向上查找super class的方法列表
        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.");
            }
            
            // 先找super class的cache
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // 在super class 的cache中找到imp,將imp存儲到當(dāng)前class(注意史侣,不是super  class)的cache中 
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            
            // 在Super class的cache中沒有找到拴泌,調(diào)用getMethodNoSuper_nolock在super class的方法列表中查找對應(yīng)的實現(xiàn)
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // 在class和其所有的super class中均未找到imp,進入動態(tài)方法解析流程resolveMethod
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

    // 如果在class惊橱,super classes和動態(tài)方法解析 都不能找到這個imp蚪腐,則進入消息轉(zhuǎn)發(fā)流程,嘗試讓別的class來響應(yīng)這個SEL
   
    // 消息轉(zhuǎn)發(fā)結(jié)束税朴,cache結(jié)果到當(dāng)前class
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();

    return imp;
}

通過上的源碼回季,我們可以很清晰的知曉runtime的消息處理流程:

嘗試在當(dāng)前receiver對應(yīng)的class的cache中查找imp
嘗試在class的方法列表中查找imp
嘗試在class的所有super classes中查找imp(先看Super class的cache,再看super class的方法列表)
上面3步都沒有找到對應(yīng)的imp正林,則嘗試動態(tài)解析這個SEL
動態(tài)解析失敗泡一,嘗試進行消息轉(zhuǎn)發(fā),讓別的class處理這個SEL

在查找class的方法列表中是否有SEL的對應(yīng)實現(xiàn)時觅廓,是調(diào)用函數(shù)getMethodNoSuper_nolock:

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    assert(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?

    for (auto mlists = cls->data()->methods.beginLists(), 
              end = cls->data()->methods.endLists(); 
         mlists != end;
         ++mlists)
    {
        method_t *m = search_method_list(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

方法實現(xiàn)很簡單鼻忠,就是在class的方法列表methods中,根據(jù)SEL查找對應(yīng)的imp哪亿。

PS:這里順便說一下Category覆蓋類原始方法的問題粥烁,由于在methods中是線性查找的,會返回第一個和SEL匹配的imp蝇棉。而在class的realizeClass方法中讨阻,會調(diào)用methodizeClass來初始化class的方法列表。在methodizeClass方法中篡殷,會將Category方法和class方法合并到一個列表钝吮,同時,會確保Category方法位于class方法前面板辽,這樣奇瘦,在runtime尋找SEL的對應(yīng)實現(xiàn)時,會先找到Category中定義的imp返回劲弦,從而實現(xiàn)了原始方法覆蓋的效果耳标。 關(guān)于Category的底層實現(xiàn),我們會Objective-C runtime機制(4)——深入理解Category中講解邑跪。

關(guān)于消息的查找次坡,可以用下圖更清晰的解釋:

runtime用isa找到receiver對應(yīng)的class,用superClass找到class的父類画畅。

這里用
藍色的表示實例方法的消息查找流程:通過類對象實例的isa查找到對象的class砸琅,進行查找。
用紫色表示類方法的消息查找流程: 通過類的isa找到類對應(yīng)的元類, 沿著元類的super class鏈一路查找轴踱。
關(guān)于元類症脂,我們在上一章中已經(jīng)提及,元類是“類的類”淫僻。因為在runtime中诱篷,類也被看做是一種對象,而對象就一定有其所屬的類雳灵,因此兴蒸,類所屬的類,被稱為類的元類(meta class)细办。

我們所定義的類方法橙凳,其實是存儲在元類的方法列表中的。

我們所定義的類方法笑撞,其實是存儲在元類的方法列表中的岛啸。

這里有一個很好玩的地方,注意到在SEL查找鏈的最上方:Root Class和Root meta Class茴肥。

我們上面說到坚踩,對于類方法,是沿著紫色的路線依次查找Super類方法列表瓤狐。一路上各個節(jié)點瞬铸,都是元類(meta class)批幌。而注意到,Root meta Class的super class竟然是Root class! 也就是說嗓节,當(dāng)在Root meta Class中找不到類方法時荧缘,會轉(zhuǎn)而到Root class中查找類方法。而在Root class中存儲的拦宣,其實都是實例方法截粗。

換句話說,我們在通過類方法的形式調(diào)用Root class中的實例方法鸵隧,在OC中绸罗, 也是可以被解析的!

比如豆瘫,在NSObject中珊蟀,有一個實例方法:

@interface NSObject <NSObject> {
…
- (IMP)methodForSelector:(SEL)aSelector;
…
}

然后,我們自定義類:

@interface MyObj : NSObject
- (void)showY;
@end

@implementation MyObj
- (void)showY {
    NSLog(@"AB");
}
@end

我們分別通過類方法的形式調(diào)用methodForSelector 和showY

[MyObj methodForSelector:@selector(test)];
 [MyObj showY];

會發(fā)現(xiàn)外驱,編譯器允許methodForSelector的調(diào)用系洛,并能夠正常運行。
而對于showY略步,則會編譯錯誤描扯。

至于原因,就是因為對于Root meta class趟薄,其實會到Root class中尋找對應(yīng)的SEL實現(xiàn)绽诚。
類似的,還有一個好玩的例子杭煎,在子類中恩够,我們重寫NSObject的respondsToSelector方法,然后通過類方法和實例方法兩種形式來調(diào)用羡铲,看看分別會發(fā)生什么情況:

類似的蜂桶,還有一個好玩的例子,在子類中也切,我們重寫NSObject的respondsToSelector方法扑媚,然后通過類方法和實例方法兩種形式來調(diào)用,看看分別會發(fā)生什么情況:

@interface NSObject <NSObject> {
...
- (BOOL)respondsToSelector:(SEL)aSelector;
...
}

@interface MyObj : NSObject
@end

@implementation MyObj
- (BOOL)respondsToSelector:(SEL)aSelector {
    NSLog(@"It is my overwrite");
    return YES;
}
@end

然后通過類方法和實例方法分別調(diào)用

[MyObj methodForSelector:@selector(test)];
 
 MyObj *obj = [[MyObj alloc] init];
 [obj showY];

這兩種調(diào)用方式會有什么不同雷恃?
這當(dāng)做一個思考題疆股,如果大家理解了上面IMP的查找流程,那么應(yīng)該能夠知道答案倒槐。

objc_msgSendSuper

看完了objc_msgSend方法的調(diào)用流程旬痹,我們再來看一下objc_msgSendSuper是如何調(diào)用的。
當(dāng)我們在代碼里面顯示的調(diào)用super 方法時,runtime就會調(diào)用objc_msgSendSuper來完成消息發(fā)送两残。

objc_msgSendSuper 的定義如下:

OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

當(dāng)我使用super關(guān)鍵字時永毅,在這里,super并不代表某個確定的對象人弓,而是編譯器的一個符號沼死,編譯器會將super替換為objc_super *類型來傳入objc_msgSendSuper方法中。

而objc_super 結(jié)構(gòu)體定義如下:

struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;
    /// Specifies the particular superclass of the instance to message. 
    __unsafe_unretained _Nonnull Class super_class;

};

第一個成員id receiver票从,表明要將消息發(fā)送給誰漫雕。它應(yīng)該是我們的類實例(注意滨嘱,是當(dāng)前類實例峰鄙,而不是super)
第二個成員Class super_class,表明要到哪里去尋找SEL所對應(yīng)的IMP太雨。它應(yīng)該是我們類實例所對應(yīng)類的super class吟榴。(即要直接到super class中尋找IMP,而略過當(dāng)前class的method list)

簡單來說囊扳,當(dāng)調(diào)用super method時吩翻,runtime會到super class中找到IMP,然后發(fā)送到當(dāng)前class的實例上锥咸。因此狭瞎,雖然IMP的實現(xiàn)是用的super class,但是搏予,最終作用對象熊锭,仍然是當(dāng)前class 的實例。這也就是為什么

NSLog(@"%@ %@",[self class],  [super class]);

會輸出同樣的內(nèi)容雪侥,即[self class]的內(nèi)容碗殷。

我們來看一下objc_msgSendSuper的匯編實現(xiàn):

ENTRY _objc_msgSendSuper
    MESSENGER_START
    
    ldr r9, [r0, #CLASS]    // r9 = struct super->class
    CacheLookup NORMAL
    // cache hit, IMP in r12, eq already set for nonstret forwarding
    ldr r0, [r0, #RECEIVER] // load real receiver
    MESSENGER_END_FAST
    bx  r12         // call imp

    CacheLookup2 NORMAL
    // cache miss
    ldr r9, [r0, #CLASS]    // r9 = struct super->class
    ldr r0, [r0, #RECEIVER] // load real receiver
    MESSENGER_END_SLOW
    b   __objc_msgSend_uncached
    
    END_ENTRY _objc_msgSendSuper

可以看到,它就是在struct super->class的method list 中尋找對應(yīng)的IMP速缨,而real receiver則是super->receiver锌妻,即當(dāng)前類實例。

如果在super class的cache中沒有找到IMP的話旬牲,則同樣會調(diào)用__objc_msgSend_uncached仿粹,這和objc_msgSend是一樣的,最終都會調(diào)用到

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

只不過原茅,這里傳入lookUpImpOrForward里面的cls牍陌,使用了super class而已。

動態(tài)解析

如果在類的繼承體系中员咽,沒有找到相應(yīng)的IMP毒涧,runtime首先會進行消息的動態(tài)解析。所謂動態(tài)解析,就是給我們一個機會契讲,將方法實現(xiàn)在運行時動態(tài)的添加到當(dāng)前的類中仿吞。然后,runtime會重新嘗試走一遍消息查找的過程:

// 在class和其所有的super class中均未找到imp捡偏,進入動態(tài)方法解析流程resolveMethod
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

在源碼中唤冈,可以看到,runtime會調(diào)用_class_resolveMethod银伟,讓用戶進行動態(tài)方法解析你虹,而且設(shè)置標記triedResolver = YES,僅執(zhí)行一次彤避。當(dāng)動態(tài)解析完畢傅物,不管用戶是否作出了相應(yīng)處理,runtime琉预,都會goto retry董饰, 重新嘗試查找一遍類的消息列表。

根據(jù)是調(diào)用的實例方法或類方法圆米,runtime會在對應(yīng)的類中調(diào)用如下方法:

+ (BOOL)resolveInstanceMethod:(SEL)sel  // 動態(tài)解析實例方法
+ (BOOL)resolveClassMethod:(SEL)sel     // 動態(tài)解析類方法

resolveInstanceMethod

  • (BOOL)resolveInstanceMethod:(SEL)sel用來動態(tài)解析實例方法卒暂,我們需要在運行時動態(tài)的將對應(yīng)的方法實現(xiàn)添加到類實例所對應(yīng)的類的消息列表中:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(singSong)) {
        class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(unrecoginzedInstanceSelector)), "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

- (void)unrecoginzedInstanceSelector {
    NSLog(@"It is a unrecoginzed instance selector");
}

resolveClassMethod

+(BOOL)resolveClassMethod:(SEL)sel用于動態(tài)解析類方法。 我們同樣需要將類的實現(xiàn)動態(tài)的添加到相應(yīng)類的消息列表中娄帖。

但這里需要注意也祠,調(diào)用類方法的‘對象’實際也是一個類,而類所對應(yīng)的類應(yīng)該是元類近速。要添加類方法诈嘿,我們必須把方法的實現(xiàn)添加到元類的方法列表中。

在這里数焊,我們就不能夠使用[self class]了永淌,它僅能夠返回當(dāng)前的類。而是需要使用object_getClass(self)佩耳,它其實會返回isa所指向的類遂蛀,即類所對應(yīng)的元類(注意,因為現(xiàn)在是在類方法里面干厚,self所指的是Class李滴,而通過object_getClass(self)獲取self的類,自然是元類)蛮瞄。

+ (BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(payMoney)) {
        class_addMethod(object_getClass(self), sel, class_getMethodImplementation(object_getClass(self), @selector(unrecognizedClassSelector)), "v@:");
        return YES;
    }
    return [class_getSuperclass(self) resolveClassMethod:sel];
}

+ (void)unrecognizedClassSelector {
    NSLog(@"It is a unrecoginzed class selector");
}

這里主要弄清楚所坯,類,元類挂捅,實例方法和類方法在不同地方存儲芹助,就清楚了。

關(guān)于class方法和object_getClass方法的區(qū)別:

當(dāng)self是實例對象時,[self class]與object_getClass(self)等價状土,因為前者會調(diào)用后者无蜂,都會返回對象實例所對應(yīng)的類。

當(dāng)self是類對象時蒙谓,[self class]返回類對象自身斥季,而object_getClass(self)返回類所對應(yīng)的元類。

消息轉(zhuǎn)發(fā)

當(dāng)動態(tài)解析失敗累驮,則進入消息轉(zhuǎn)發(fā)流程酣倾。所謂消息轉(zhuǎn)發(fā),是將當(dāng)前消息轉(zhuǎn)發(fā)到其它對象進行處理谤专。

- (id)forwardingTargetForSelector:(SEL)aSelector  // 轉(zhuǎn)發(fā)實例方法
+ (id)forwardingTargetForSelector:(SEL)aSelector  // 轉(zhuǎn)發(fā)類方法躁锡,id需要返回類對象


- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if(aSelector == @selector(mysteriousMethod:)){
        return alternateObject;
    }
    return [super forwardingTargetForSelector:aSelector];
}


+ (id)forwardingTargetForSelector:(SEL)aSelector {
    if(aSelector == @selector(xxx)) {
        return NSClassFromString(@"Class name");
    }
    return [super forwardingTargetForSelector:aSelector];
}


如果forwardingTargetForSelector沒有實現(xiàn),或返回了nil或self毒租,則會進入另一個轉(zhuǎn)發(fā)流程稚铣。
它會依次調(diào)用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector箱叁,然后runtime會根據(jù)該方法返回的值墅垮,組成一個NSInvocation對象,并調(diào)用- (void)forwardInvocation:(NSInvocation *)anInvocation 耕漱。注意算色,當(dāng)調(diào)用到forwardInvocation時,無論我們是否實現(xiàn)了該方法螟够,系統(tǒng)都默認消息已經(jīng)得到解析灾梦,不會引起crash。

20180604174204357.png

注意妓笙,和動態(tài)解析不同若河,由于消息轉(zhuǎn)發(fā)實際上是將消息轉(zhuǎn)發(fā)給另一種對象處理。而動態(tài)解析仍是嘗試在當(dāng)前類范圍內(nèi)進行處理寞宫。

消息轉(zhuǎn)發(fā) & 多繼承

通過消息轉(zhuǎn)發(fā)流程萧福,我們可以模擬實現(xiàn)OC的多繼承機制。詳情可以參考 官方文檔 辈赋。

2018060417455566.png

直接調(diào)用IMP

runtime的消息解析鲫忍,究其根本,實際上就是根據(jù)SEL查找到對應(yīng)的IMP钥屈,并調(diào)用之悟民。如果我們可以直接知道IMP的所在,就不用再走消息機制這一層了篷就。似乎不走消息機制會提高一些方法調(diào)用的速度射亏,但現(xiàn)實是這樣的嗎?

我們比較一下:

CGFloat BNRTimeBlock (void (^block)(void)) {
    mach_timebase_info_data_t info;
    if (mach_timebase_info(&info) != KERN_SUCCESS) return -1.0;

    uint64_t start = mach_absolute_time ();
    block ();
    uint64_t end = mach_absolute_time ();
    uint64_t elapsed = end - start;

    uint64_t nanos = elapsed * info.numer / info.denom;
    return (CGFloat)nanos / NSEC_PER_SEC;

} // BNRTimeBlock

 Son *mySon1 = [Son new];
 setter ss = (void (*)(id, SEL, BOOL))[mySon1 methodForSelector:@selector(setFilled:)];
    CGFloat timeCost1 = BNRTimeBlock(^{
        for (int i = 0; i < 1000; ++i) {
            ss(mySon1, @selector(setFilled:), YES);
        }
    });
    
CGFloat timeCost2 = BNRTimeBlock(^{
        for (int i = 0; i < 1000; ++i) {
            [mySon1 setFilled:YES];
        }
    });

將timeCost1和timeCost2打印出來,你會發(fā)現(xiàn)智润,僅僅相差0.000001秒银锻,幾乎可以忽略不計。這樣是因為在消息機制中做鹰,有緩存的存在击纬。


總結(jié)

在本文中,我們了解了OC語言中方法調(diào)用實現(xiàn)的底層機制——消息機制钾麸。并了解了self method和super method的異同更振。
最后,讓我們回答文章開頭的兩個問題:

類實例可以調(diào)用類方法嗎饭尝? 類可以調(diào)用實例方法嗎肯腕? 為什么?
類實例不可用調(diào)用類方法钥平,因為類實例查找消息IMP的流程僅會沿著繼承鏈查找class 的method list实撒,而對于類方法來說,是存于meta class的method list 的涉瘾,因此類實例通過objc_msgSend方法是找不到對應(yīng)的實現(xiàn)的知态。

類大多數(shù)情況下是不能夠調(diào)用實例方法的,除非實例方法定義在root class——NSObject中立叛。因為负敏,當(dāng)調(diào)用類方法時,會在meta class的繼承鏈的method list 查找對應(yīng)的IMP秘蛇,而root meta class對應(yīng)的super class是NSObject其做,因此在NSObject中定義的實例方法,其實是可以通過類方法形式來調(diào)用的赁还。

下面代碼輸出什么妖泄?

@interface Father : NSObject
@end

@implementation Father
@end

@interface Son : Father
- (void)showClass;
@end

@implementation Son
- (void)showClass {
    NSLog(@"self class = %@, super class = %@", [self class], [super class]);
}

...
Son *son = [Son new];
[son showClass];    // 這里輸出什么?
...

會輸出

self class = Son, super class = Son
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末艘策,一起剝皮案震驚了整個濱河市蹈胡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌柬焕,老刑警劉巖审残,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異斑举,居然都是意外死亡搅轿,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門富玷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來璧坟,“玉大人既穆,你說我怎么就攤上這事∪妇椋” “怎么了幻工?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長黎茎。 經(jīng)常有香客問我囊颅,道長,這世上最難降的妖魔是什么傅瞻? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任踢代,我火速辦了婚禮,結(jié)果婚禮上嗅骄,老公的妹妹穿的比我還像新娘胳挎。我一直安慰自己,他們只是感情好溺森,可當(dāng)我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布慕爬。 她就那樣靜靜地躺著,像睡著了一般屏积。 火紅的嫁衣襯著肌膚如雪医窿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天肾请,我揣著相機與錄音留搔,去河邊找鬼更胖。 笑死铛铁,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的却妨。 我是一名探鬼主播饵逐,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼彪标!你這毒婦竟也來了倍权?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤捞烟,失蹤者是張志新(化名)和其女友劉穎薄声,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體题画,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡默辨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了苍息。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缩幸。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡壹置,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出表谊,到底是詐尸還是另有隱情钞护,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布爆办,位于F島的核電站难咕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏距辆。R本人自食惡果不足惜步藕,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧境钟,春花似錦抹凳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挫望。三九已至立润,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間媳板,已是汗流浹背桑腮。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蛉幸,地道東北人破讨。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像奕纫,于是被迫代替她去往敵國和親提陶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容

  • 當(dāng)我們用中括號[]調(diào)用OC函數(shù)的時候匹层,實際上會進入消息發(fā)送和消息轉(zhuǎn)發(fā)流程: 消息發(fā)送(Messaging)隙笆,run...
    無忘無往閱讀 591評論 0 5
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個「動態(tài)」表現(xiàn)在哪呢升筏?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,182評論 0 7
  • 本文詳細整理了 Cocoa 的 Runtime 系統(tǒng)的知識撑柔,它使得 Objective-C 如虎添翼,具備了靈活的...
    lylaut閱讀 795評論 0 4
  • 文中的實驗代碼我放在了這個項目中您访。 以下內(nèi)容是我通過整理[這篇博客] (http://yulingtianxia....
    茗涙閱讀 914評論 0 6
  • 消息發(fā)送和轉(zhuǎn)發(fā)流程可以概括為:消息發(fā)送(Messaging)是 Runtime 通過 selector 快速查找 ...
    lylaut閱讀 1,827評論 2 3