七、消息流程之快速查找

1、方法調(diào)用本質(zhì)

如下圖中展示的對象調(diào)用方法,在底層是怎么調(diào)的呢:

image.png

為了探索這個問題,用clangmain.m文件編譯為C++文件,指令為:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk xxxxx.m
編譯后生成一個后綴為.cpp的文件,這個文件代碼非常多,只需看main函數(shù)里面即可,可以看到main函數(shù)編譯后變?yōu)椋?/p>

image.png

可以看到所有的方法調(diào)用都轉(zhuǎn)化為objc_msgSend,也就是經(jīng)常聽到的消息轉(zhuǎn)發(fā),導(dǎo)入頭文件#import <objc/message.h>,我們先看看定義:

image.png

默認(rèn)有兩個參數(shù):第一個是消息接受者,第二個參數(shù)是SEL,了解這個可以改一下main函數(shù)的方法調(diào)用方式:

image.png

這里報一個參數(shù)的錯誤,需要修改一下配置:

image.png

修改之后運行一下:

image.png

跟直接調(diào)用方法效果是一樣的。

子類怎么調(diào)父類的方法呢,這個要介紹另一個消息發(fā)送方式objc_msgSendSuper,從其定義來看他也需要兩個參數(shù):第一個是結(jié)構(gòu)體objc_super的指針,第二個是sel,結(jié)構(gòu)體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 */
};

使用方法如下:

image.png

2、objc_msgSend流程

objc_msgSend使用匯編寫的,主要目的是為了快,因為方法的調(diào)用是非常頻繁的,直接用匯編可以縮短調(diào)用時間。
簡單分析其流程,可以全局搜索找到調(diào)用入口:

image.png

來逐行分析一下其含義:
1、ENTRY _objc_msgSend:進(jìn)入objc_msgSend流程
2、cmp p0, #0:cmp(compare)代表比較,就是用p0和0比較。p0表示第一個寄存器的值,根據(jù)objc_msgSend的定義其第一個參數(shù)是消息接受者,也就是說如果消息接收者為nil,就走到下面的跳轉(zhuǎn)流程了。
3、

#if SUPPORT_TAGGED_POINTERS
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
    b.eq    LReturnZero
#endif

這一段代碼的意思是消息接受者為nil,如果是SUPPORT_TAGGED_POINTERS就跳轉(zhuǎn)到LNilOrTagged,否則跳轉(zhuǎn)到LReturnZero,這個是異常情況,就不深入分析了。
4、ldr p13, [x0]:將x0寄存器的數(shù)據(jù)讀到寄存器p13中,也就是說將消息接收者放到p13中。
5、GetClassFromIsa_p16 p13, 1, x0:這個在當(dāng)前文件中搜一下GetClassFromIsa_p16,可以看到:

image.png

其實GetClassFromIsa_p16就是一個宏定義,這個作用就是根據(jù)isa通過移位獲取到class,這個原理在中介紹過,這里就不介紹了。最終就是將class存到p16中了。
6、

LGetIsaDone:
    // calls imp or objc_msgSend_uncached
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

通過上面5步將消息接受者的class找到,接下來就開始緩存查找了,同樣找到CacheLookup的定義:

image.png

這個是objc818版本比之前的版本改動不少,把沒有用的去掉簡化一下;

.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant


    mov x15, x16            // stash the original isa
LLookupStart\Function:

    ldr p11, [x16, #CACHE]          // p11 = mask|buckets

    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
    and p12, p1, p11, LSR #48       // x12 = _cmd & mask

    add p13, p10, p12, LSL #(1+PTRSHIFT)
                        // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

                        // do {
1:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    cmp p9, p1              //     if (sel != _cmd) {
    b.ne    3f              //         scan more
                        //     } else {
2:  CacheHit \Mode              // hit:    call or return imp
                        //     }
3:  cbz p9, \MissLabelDynamic       //     if (sel == 0) goto Miss;
    cmp p13, p10            // } while (bucket >= buckets)
    b.hs    1b

.endmacro

6.1 ldr p11, [x16, #CACHE],x16加上#CACHE的值給p11。看一下#CASE定義就知道其大小是16,x16存的是isa的地址也是類的首地址,類的首地址+16字節(jié)就是cache,參考中cache的獲取方法。

image.png

6.2 p10, p11, #0x0000ffffffffffff,p11 & #0x0000ffffffffffff的值放到p10,在里面已經(jīng)介紹過cache的高16位是存的mask,第48位存的是buckets,p10存的就是buckets了
6.3 and p12, p1, p11, LSR #48,p11, LSR #48的意思是P11 邏輯右移48位取到的結(jié)果&p1,p11邏輯右移48位就是mask,p1就是_cmd,所以p12的值就是(_cmd&mask)在里面已經(jīng)介紹過這個值就是buckets的腳標(biāo),可以根據(jù)它獲取到相應(yīng)的bucket。
6.4 add p13, p10, p12, LSL #(1+PTRSHIFT),這個的意思是將p10加p12邏輯左移(1+PTRSHIFT)放到P13中。1+PTRSHIFT的值是4:

image.png

p12<<4就是將腳標(biāo)放大2^4,p10buckets也是首地址,p13存的就是當(dāng)前bucket,舉個例子腳標(biāo)為2

image.png

這樣就取到bucket3了,bucket的大小是16因為結(jié)構(gòu)體bucket_t里面包含兩個指針selimp

7、下面進(jìn)入一個循環(huán)體了:

                        // do {
1:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    cmp p9, p1              //     if (sel != _cmd) {
    b.ne    3f              //         scan more
                        //     } else {
2:  CacheHit \Mode              // hit:    call or return imp
                        //     }
3:  cbz p9, \MissLabelDynamic       //     if (sel == 0) goto Miss;
    cmp p13, p10            // } while (bucket >= buckets)
    b.hs    1b

7.1 ldp p17, p9, [x13], #-BUCKET_SIZE,取到當(dāng)前的sel后,然后backet--,
7.2 cmp p9, p1,當(dāng)前的sel和_cmd比較,如果相同就返回imp,如果不相同跳到下一次循環(huán)
7.3 CacheHit \Mode,命中imp返回
7.4 cbz p9, \MissLabelDynamic,沒有找到imp開始到methodlist里面查找——消息慢速查找流程,MissLabelDynamic是第三個參數(shù)__objc_msgSend_uncached,看一下其定義:

image.png

7.5 cmp p13, p10,循環(huán)條件,就是判斷當(dāng)前bucket是不是已經(jīng)到了buckets的首地址,如果已經(jīng)到了就調(diào)出循環(huán)
7.6 b.hs 1b,再次進(jìn)入循環(huán)體。因為bucket已經(jīng)小于buckets了,所以sel=0,就會調(diào)出去了。

這一句不是很理解,跟之前的流程不一樣了,可以在評論區(qū)探討一下

消息流程的快速查找就介紹完了,匯編也是半瓢水,有紕漏的地方希望指出。后面再介紹消息流程的慢速查找。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容