OC消息機(jī)制和super關(guān)鍵字

閱讀原文請到我的博客OC消息機(jī)制和super關(guān)鍵字

消息發(fā)送

在Objective-C里面調(diào)用一個方法[object method],運(yùn)行時會將它翻譯成objc_msgSend(id self, SEL op, ...)的形式。

objc_msgSend

objc_msgSend的實(shí)現(xiàn)在objc-msg-arm.s昆码、objc-msg-arm64.s等文件中,是通過匯編實(shí)現(xiàn)的延塑。這里主要看在arm64objc-msg-arm64.s的實(shí)現(xiàn)蠢护。由于匯編不熟堤结,里面的實(shí)現(xiàn)只能連看帶猜必怜。

    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:
    /* nil check洼畅,如果為空就是調(diào)用LReturnZero,LReturnZero里調(diào)用MESSENGER_END_NIL*/
    b.eq    LReturnZero     // nil check

    // tagged
    mov x10, #0xf000000000000000
    cmp x0, x10
    b.hs    LExtTag
    adrp    x10, _objc_debug_taggedpointer_classes@PAGE
    add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
    ubfx    x11, x0, #60, #4
    ldr x16, [x10, x11, LSL #3]
    b   LGetIsaDone

LExtTag:
    // ext tagged
    adrp    x10, _objc_debug_taggedpointer_ext_classes@PAGE
    add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
    ubfx    x11, x0, #52, #8
    ldr x16, [x10, x11, LSL #3]
    b   LGetIsaDone
    
LReturnZero:
    // x0 is already zero
    mov x1, #0
    movi    d0, #0
    movi    d1, #0
    movi    d2, #0
    movi    d3, #0
    MESSENGER_END_NIL
    ret

    END_ENTRY _objc_msgSend

上面的流程可能是這樣的:


objc_msgsend

CacheLookup的注釋有兩處:

  1. calls imp or objc_msgSend_uncached
  2. Locate the implementation for a selector in a class method cache.

即使看不懂匯編代碼棚赔,但是從上面的注釋我們可以猜測,消息機(jī)制會先從緩存中去查找。

__objc_msgSend_uncached

通過方法名我們可以知道靠益,沒有緩存的時候應(yīng)該會執(zhí)行__objc_msgSend_uncached丧肴。

    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

這里的MethodTableLookup里涉及到objc-runtime-new.mm文件中的_class_lookupMethodAndLoadCache3。該函數(shù)會調(diào)用lookUpImpOrForward函數(shù)胧后。

lookUpImpOrForward

lookUpImpOrForward會返回一個imp芋浮,它的函數(shù)實(shí)現(xiàn)比較長,但是注釋寫的非常清楚壳快。它的實(shí)現(xiàn)主要由以下幾步(這里直接從緩存獲取開始):

  1. 通過cache_getImp從緩存中獲取方法纸巷,有則返回,否則進(jìn)入第2步眶痰;
  2. 通過getMethodNoSuper_nolock從類的方法列表中獲取瘤旨,有加入緩存中并返回,否則進(jìn)入第3步竖伯;
  3. 通過父類的緩存和父類的方法列表中尋找是否有對應(yīng)的imp存哲,此時會進(jìn)入一個for循環(huán),沿著類的父類一直往上找七婴,直接找到NSObject為止祟偷。如果找到返回,否則進(jìn)入第4步打厘;
  4. 進(jìn)入方法決議(method resolve)的過程即調(diào)用_class_resolveMethod修肠,如果失敗,進(jìn)入第5步户盯;
  5. 在緩存嵌施、當(dāng)前類、父類以及方法決議都沒有找到的情況下先舷,Objective-C還為我們提供了最后一次翻身的機(jī)會艰管,調(diào)用_objc_msgForward_impcache進(jìn)行方法轉(zhuǎn)發(fā),如果找到便加入緩存蒋川;如果沒有就crash牲芋。

上述過程中有幾個比較重要的函數(shù):

_class_resolveMethod

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);
        }
    }
}

上述函數(shù)會根據(jù)當(dāng)前傳入的類的是不是一個元類,在_class_resolveInstanceMethod_class_resolveClassMethod中選擇一個進(jìn)行調(diào)用捺球。注釋也說明了這兩個方法的作用就是判斷當(dāng)前類是否實(shí)現(xiàn)了 resolveInstanceMethod:或者resolveClassMethod:方法缸浦,然后用objc_msgSend執(zhí)行上述方法。

_class_resolveClassMethod

_class_resolveClassMethod_class_resolveInstanceMethod實(shí)現(xiàn)類似氮兵,這里就只看_class_resolveClassMethod的實(shí)現(xiàn)裂逐。

static void _class_resolveClassMethod(Class cls, SEL sel, id inst) {
    assert(cls->isMetaClass());

    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) {
         //沒有找到resolveClassMethod方法,直接返回泣栈。
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                        SEL_resolveClassMethod, sel);

    // 緩存結(jié)果
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
    // 以下代碼省略不影響閱讀                          
}

_objc_msgForward_impcache

    STATIC_ENTRY __objc_msgForward_impcache

    MESSENGER_START
    nop
    MESSENGER_END_SLOW

    // No stret specialization.
    b   __objc_msgForward

    END_ENTRY __objc_msgForward_impcache

    ENTRY __objc_msgForward

    adrp    x17, __objc_forward_handler@PAGE
    ldr x17, [x17, __objc_forward_handler@PAGEOFF]
    br  x17
    
    END_ENTRY __objc_msgForward

_objc_msgForward_impcache用來進(jìn)行消息轉(zhuǎn)發(fā)卜高,但是其真正的核心是調(diào)用_objc_msgForward弥姻。

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

關(guān)于_objc_msgForwardobjc中并沒有其相關(guān)實(shí)現(xiàn),只能看到_objc_forward_handler掺涛。其實(shí)_objc_msgForward的實(shí)現(xiàn)是在CFRuntime.c中的庭敦,但是開源出來的CFRuntime.c并沒有相關(guān)實(shí)現(xiàn),但是也不影響我們對真理的追求薪缆。

我們做幾個實(shí)驗(yàn)來驗(yàn)證消息轉(zhuǎn)發(fā)秧廉。

消息重定向測試

// .h文件
@interface AObject : NSObject

- (void)sendMessage;

@end
// .m文件
@implementation AObject

/** 驗(yàn)證消息重定向 */
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(sendMessage)) {
         return [BObject new];
    }

    return [super forwardingTargetForSelector:aSelector];
}

@end

// .h文件
@interface BObject : NSObject

- (void)sendMessage;

@end

// .m文件
@implementation BObject

- (void)sendMessage {
    NSLog(@"%@ send message", self.class);
}

@end

// 調(diào)用
AObject *a = [AObject new];
[a sendMessage];

運(yùn)行結(jié)果:

2019-03-12 10:18:54.252949+0800 iOSCodeLearning[18165:5967575] BObject send message

forwardingTargetForSelector:處打個斷點(diǎn),查看一下調(diào)用棧:

message_redirection

_CF_forwarding_prep_0___forwarding___這兩個方法會先被調(diào)用了拣帽,之后調(diào)用了forwardingTargetForSelector:疼电。

方法簽名測試

// .h文件
@interface AObject : NSObject

- (void)sendMessage;

@end
// .m文件
@implementation AObject

/** 消息重定向 */
- (id)forwardingTargetForSelector:(SEL)aSelector {
   return nil;
}

/** 方法簽名測試 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(sendMessage)) {
        return [BObject instanceMethodSignatureForSelector:@selector(sendMessage)];
    }
    
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL selector = [anInvocation selector];
    if (selector == @selector(sendMessage)) {
        [anInvocation invokeWithTarget:[BObject new]];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

@end

// .h文件
@interface BObject : NSObject

- (void)sendMessage;

@end

// .m文件
@implementation BObject

- (void)sendMessage {
    NSLog(@"%@ send message", self.class);
}

@end

// 調(diào)用
AObject *a = [AObject new];
[a sendMessage];
method_signature

代碼執(zhí)行結(jié)果和消息重定向測試的運(yùn)行結(jié)果一致。_CF_forwarding_prep_0___forwarding___這兩個方法又再次被調(diào)用了减拭,之后代碼會先執(zhí)行forwardingTargetForSelector:(消息重定向)蔽豺,消息重定向如果失敗后調(diào)用methodSignatureForSelector:forwardInvocation:方法簽名。所以說___forwarding___方法才是消息轉(zhuǎn)發(fā)的真正實(shí)現(xiàn)峡谊。

crash測試

// .h文件
@interface AObject : NSObject

- (void)sendMessage;

@end
// .m文件
@implementation AObject

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return nil;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
}

/** 驗(yàn)證Crash */
- (void)doesNotRecognizeSelector:(SEL)aSelector {
    if (aSelector == @selector(sendMessage)) {
        NSLog(@"%@ doesNotRecognizeSelector", self.class);
    }
}

@end

// .h文件
@interface BObject : NSObject

- (void)sendMessage;

@end

// .m文件
@implementation BObject

- (void)sendMessage {
    NSLog(@"%@ send message", self.class);
}

@end

// 調(diào)用
AObject *a = [AObject new];
[a sendMessage];

代碼運(yùn)行結(jié)果肯定是crash茫虽,結(jié)合上面的代碼我們知道消息轉(zhuǎn)發(fā)會調(diào)用___forwarding___這個內(nèi)部方法。___forwarding___方法調(diào)用順序是forwardingTargetForSelector:->methodSignatureForSelector:->doesNotRecognizeSelector:

我們用一張圖表示整個消息發(fā)送的過程:


消息機(jī)制流程圖

super關(guān)鍵字

我們先查看一下執(zhí)行[super init]的時候既们,調(diào)用了那些方法

super_init

objc_msgSendSuper2的聲明在objc-abi.h

// objc_msgSendSuper2() takes the current search class, not its superclass.
OBJC_EXPORT id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0, 2.0);

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. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};

從上面的定義我們可以知道receiver即消息的實(shí)際接收者濒析,
super_class為指向當(dāng)前類的父類。

所以該函數(shù)實(shí)際的操作是:從objc_super結(jié)構(gòu)體指向的super_class開始查找啥纸,直到會找到NSObject的方法為止号杏。找到后以receiver去調(diào)用。當(dāng)然整個查找的過程還是和消息發(fā)送的流程一樣斯棒。

所以我們能理解為什么下面這段代碼執(zhí)行的結(jié)果都是AObject了吧盾致。雖然使用[super class],但是真正執(zhí)行方法的對象還是AObject荣暮。

// 代碼
@implementation AObject

- (instancetype)init {
    if (self = [super init]) {
        NSLog(@"%@", [super class]);
        NSLog(@"%@", [self class]);
    }
    
    return self;
}

@end

// 執(zhí)行結(jié)果
2019-03-12 19:44:46.003313+0800 iOSCodeLearning[34431:7234182] AObject
2019-03-12 19:44:46.003442+0800 iOSCodeLearning[34431:7234182] AObject
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末庭惜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子穗酥,更是在濱河造成了極大的恐慌护赊,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,946評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砾跃,死亡現(xiàn)場離奇詭異骏啰,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)抽高,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評論 3 399
  • 文/潘曉璐 我一進(jìn)店門判耕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人翘骂,你說我怎么就攤上這事壁熄≈愫溃” “怎么了?”我有些...
    開封第一講書人閱讀 169,716評論 0 364
  • 文/不壞的土叔 我叫張陵请毛,是天一觀的道長志鞍。 經(jīng)常有香客問我,道長方仿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,222評論 1 300
  • 正文 為了忘掉前任统翩,我火速辦了婚禮仙蚜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘厂汗。我一直安慰自己委粉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,223評論 6 398
  • 文/花漫 我一把揭開白布娶桦。 她就那樣靜靜地躺著贾节,像睡著了一般。 火紅的嫁衣襯著肌膚如雪衷畦。 梳的紋絲不亂的頭發(fā)上栗涂,一...
    開封第一講書人閱讀 52,807評論 1 314
  • 那天,我揣著相機(jī)與錄音祈争,去河邊找鬼斤程。 笑死,一個胖子當(dāng)著我的面吹牛菩混,可吹牛的內(nèi)容都是我干的忿墅。 我是一名探鬼主播,決...
    沈念sama閱讀 41,235評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼沮峡,長吁一口氣:“原來是場噩夢啊……” “哼疚脐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起邢疙,我...
    開封第一講書人閱讀 40,189評論 0 277
  • 序言:老撾萬榮一對情侶失蹤棍弄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后秘症,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體照卦,經(jīng)...
    沈念sama閱讀 46,712評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,775評論 3 343
  • 正文 我和宋清朗相戀三年乡摹,在試婚紗的時候發(fā)現(xiàn)自己被綠了役耕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,926評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡聪廉,死狀恐怖瞬痘,靈堂內(nèi)的尸體忽然破棺而出故慈,到底是詐尸還是另有隱情,我是刑警寧澤框全,帶...
    沈念sama閱讀 36,580評論 5 351
  • 正文 年R本政府宣布察绷,位于F島的核電站,受9級特大地震影響津辩,放射性物質(zhì)發(fā)生泄漏拆撼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,259評論 3 336
  • 文/蒙蒙 一喘沿、第九天 我趴在偏房一處隱蔽的房頂上張望闸度。 院中可真熱鬧,春花似錦蚜印、人聲如沸莺禁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哟冬。三九已至,卻和暖如春忆绰,著一層夾襖步出監(jiān)牢的瞬間浩峡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評論 1 274
  • 我被黑心中介騙來泰國打工较木, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留红符,地道東北人。 一個月前我還...
    沈念sama閱讀 49,368評論 3 379
  • 正文 我出身青樓伐债,卻偏偏與公主長得像预侯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子峰锁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,930評論 2 361

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

  • 一年當(dāng)中萎馅,春總是寫的最多,因?yàn)檫@是萬物萌生的季節(jié)虹蒋,但夏也好不遜色于春糜芳,也獨(dú)具魅力,別具一格魄衅。 一場夏天的雨過后峭竣,便...
    phantomses閱讀 633評論 1 0
  • 簡書首發(fā),更多精彩晃虫,敬請期待皆撩。 互聯(lián)網(wǎng)時代,信息多如牛毛,憑什么要讀你的文章扛吞? 因?yàn)槲业奈恼屡1七埂?什么是牛逼的...
    石頭讀書會閱讀 1,388評論 8 55
  • 題材:科幻/愛情/現(xiàn)代/校園 人物介紹:男主:齊晟/凌星 女主:沐然/凌靈 男配:張窕 宋梓文 李天明 女配:胡小...
    Dec艷閱讀 378評論 2 1