iOS-底層原理08-msgSend(cache_t)

《iOS底層原理文章匯總》

運行時感受歌逢,通過clang底層編譯

編譯前的代碼
#import <Foundation/Foundation.h>
@interface DCPerson :NSObject
-(void)sayNB;
-(void)sayHello;
@end

@implementation DCPerson
-(void)sayNB{
    NSLog(@"%s",__func__);
}
-(void)sayHello{
    NSLog(@"%s",__func__);
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        DCPerson *person = [DCPerson alloc];
        [person sayNB];
        [person sayHello];
        
        NSLog(@"Hello, World!");
    }
    return 0;
}

編譯后的代碼
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        DCPerson *person = ((DCPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("DCPerson"), sel_registerName("alloc"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_0d_yy9dm1g92sjdnrzk1pn6cgf80000gn_T_main_9aaf89_mi_2);
    }
    return 0;
}
  • 1.調(diào)用sayNB方法進行等價互換objc_msgSend(person,sel_registerName("sayNB"));
objc_msgSend(person,sel_registerName("sayNB"));

  • 2.使用objc_msgSendSuper
@interface Anthropoid : NSObject
-(void)sayHello;
@end

@implementation Anthropoid
-(void)sayHello{
    NSLog(@"%s",__func__);
}
@end

@interface DCPerson :Anthropoid
-(void)sayNB;
@end

@implementation DCPerson
-(void)sayNB{
    NSLog(@"%s",__func__);
}
@end

//消息的接受者還是自己 - 父類 - 直接找父類
struct objc_super lgsuper;
lgsuper.receiver = person;
lgsuper.super_class = [Anthropoid class];
objc_msgSendSuper(&lgsuper, sel_registerName("sayHello"));
//輸出
-[Anthropoid sayHello]

OC 方法 - 消息 (sel imp) sel -> imp -> 內(nèi)容

isa流程圖.png

objc_msgSend流程分析.png

對象 - ISA - 方法(類) - cache_t - method_list(bits)

偽代碼實現(xiàn)

[person sayHello] -> imp (cache -> bucket (sel imp))
//獲取當前的對象
id person = 0x100000
//獲取isa
isa_t isa = 0x000000
//isa -> class -> cache
cache_t cache = isa + 16字節(jié)

//arm64
//mask|buckets 在一起的
buckets = cache & 0x0000ffffffffffff
//獲取mask
mask = cache LSR #48
//下標 = mask * & sel
index = mask & p1
//bucket 從buckets遍歷的開始(起始查詢的bucket)
bucket = buckets + index * 16 (sel imp = 16)

int count = 0;
//CheckMiss $0

do{
  if(count == 2) goto CheckMiss
  if(bucket == buckets){//進入第二層判斷
    //bucket == 第一個元素
    //bucket人為設置到最后一個元素
    bucket = buckets + mask * 16;
    count++;  
  }
  // {imp,sel} = * --bucket
  //緩存的查找的順序是:向前查找
  bucket --;
  imp = bucket.imp;
  sel = bucket.sel;
}while(bucket.sel != _cmd)//bucket里面的sel是否匹配_cmd

//CacheHit $0
return imp;

CheckMiss:
    CheckMiss(normal)

偽代碼自己完善下?妖爷??

buckets@2x.png

一個bucket中存儲了16個字節(jié)的數(shù)據(jù)晃虫,因為sel和imp分別占用8字節(jié)

p12 = buckets + (mask << 1 + PTRSHIFT)表示直接索引到最后一個元素

/*
 //偽代碼實現(xiàn)
 LLookupStart$1:

     // p1 = SEL, p16 = isa
     ldr    p11, [x16, #CACHE]                // p11 = mask|buckets

 #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
     and    p10, p11, #0x0000ffffffffffff    // p10 = buckets
     and    p12, p1, p11, LSR #48        // x12 = _cmd & mask
 #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
     and    p10, p11, #~0xf            // p10 = buckets
     and    p11, p11, #0xf            // p11 = maskShift
     mov    p12, #0xffff
     lsr    p11, p12, p11                // p11 = mask = 0xffff >> p11
     and    p12, p1, p11                // x12 = _cmd & mask
 #else
 #error Unsupported cache mask storage for ARM64.
 #endif


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

     ldp    p17, p9, [x12]        // {imp, sel} = *bucket
 1:    cmp    p9, p1            // if (bucket->sel != _cmd)
     b.ne    2f            //     scan more
     CacheHit $0            // call or return imp
     
 2:    // not hit: p12 = not-hit bucket
     CheckMiss $0            // miss if bucket->sel == 0
     cmp    p12, p10        // wrap if bucket == buckets
     b.eq    3f
     ldp    p17, p9, [x12, #-BUCKET_SIZE]!    // {imp, sel} = *--bucket
     b    1b            // loop

 3:    // wrap: p12 = first bucket, w11 = mask
 #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
     add    p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
                     // p12 = buckets + (mask << 1+PTRSHIFT)
 #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
     add    p12, p12, p11, LSL #(1+PTRSHIFT)
                     // p12 = buckets + (mask << 1+PTRSHIFT)
 #else
 #error Unsupported cache mask storage for ARM64.
 #endif

     // Clone scanning loop to miss instead of hang when cache is corrupt.
     // The slow path may detect any corruption and halt later.

     ldp    p17, p9, [x12]        // {imp, sel} = *bucket
 1:    cmp    p9, p1            // if (bucket->sel != _cmd)
     b.ne    2f            //     scan more
     CacheHit $0            // call or return imp
     
 2:    // not hit: p12 = not-hit bucket
     CheckMiss $0            // miss if bucket->sel == 0
     cmp    p12, p10        // wrap if bucket == buckets
     b.eq    3f
     ldp    p17, p9, [x12, #-BUCKET_SIZE]!    // {imp, sel} = *--bucket
     b    1b            // loop

 LLookupEnd$1:
 LLookupRecover$1:
 3:    // double wrap
     JumpMiss $0
 */

[person sayHello] -> imp(cache -> bucket(sel,imp))
//獲取當前對象
id person = 0x1000000
//獲取isa
isa = 0x00000001
//isa -> class -> cache
cache_t cache = isa + 16字節(jié)
//arm64
//mask|buckets在一起的
buckets = cache & 0x0000ffffffffffff
//獲取mask
mask = cache LSR(邏輯右移) #48位
//下標 = mask & sel
index = mask & p1
//獲取單個的bucket迄汛,從buckets,遍歷的開始(起始查詢的bucket)
bucket = buckets + ((_cmd & mask) << (1+PTRSHIFT))
bucket = buckets + index * 16(sel,imp分別8字節(jié))
while(bucket.sel != _cmd){//bucket里面的sel是否匹配_cmd
    //不滿足,沒有找到_cmd進入第二層判斷
    if(bucket == buckets){
        //bucket == 第一個元素
        //bucket 人為設置到最后一個元素
        p12 = buckets + (mask << 1+PTRSHIFT)
        bucket = buckets + mask * 16(從后往前查找槽畔,減減(--操作))
    }//{imp,sel} = *--bucket,緩存查找的順序:向前查找
    bucket--;
    imp = bucket.imp;
    sel = bucket.sel;
}
return bucket.imp;//跳出循環(huán)即找到緩存中的方法栈妆,返回bucket.imp
goto CheckMiss:CheckMiss(normal)

消息發(fā)送到匯編之后,進入入口函數(shù)ENTRY,之后如何找到當前的類厢钧?

ENTRY _objc_msgSend,之后通過ISA找到類

getClassFromIsa@2x.png
ldr p13, [x0]       // p13 = isa
GetClassFromIsa_p16 p13     // p16 = class
isa得到類@2x.png

查看GetClassFromIsa_p16,$0表示當前對象傳過來的第一個參數(shù)為isa,和ISA_MASK(掩碼)進行與運算鳞尔,放到P16的位置

.macro GetClassFromIsa_p16 /* src */

#if SUPPORT_INDEXED_ISA
    // Indexed isa
    mov p16, $0         // optimistically set dst = src
    tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f  // done if not non-pointer isa
    // isa in p16 is indexed
    adrp    x10, _objc_indexed_classes@PAGE
    add x10, x10, _objc_indexed_classes@PAGEOFF
    ubfx    p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS  // extract index
    ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:

#elif __LP64__
    // 64-bit packed isa
    and p16, $0, #ISA_MASK

#else
    // 32-bit raw isa
    mov p16, $0

#endif
LP64@2x.png

CacheLookup:若bucket==buckets,表示是第一個元素早直,則到3f,將bucket移到最后一個元素寥假,比較當前是否是要查找的_cmd == bucket.imp,若是霞扬,緩存命中直接返回糕韧,若不是,則跳2f,先判斷是否遺失(是否為空喻圃,sel是否存在(bucket.sel == 0))萤彩,若不存在且bucket == buckets,即當前bucket為第一個元素(頂層),則跳JumpMiss $0斧拍,表示遺失了對象的方法即沒找到雀扶,若bucket != buckets,則向前繼續(xù)查找肆汹,進行bucket--愚墓,往前查找予权。

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

    ldp p17, p9, [x12]      // {imp, sel} = *bucket
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
    CheckMiss $0            // miss if bucket->sel == 0
    cmp p12, p10        // wrap if bucket == buckets
    b.eq    3f
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    b   1b          // loop

3:  // wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
                    // p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    add p12, p12, p11, LSL #(1+PTRSHIFT)
                    // p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif

    // Clone scanning loop to miss instead of hang when cache is corrupt.
    // The slow path may detect any corruption and halt later.

    ldp p17, p9, [x12]      // {imp, sel} = *bucket
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
    CheckMiss $0            // miss if bucket->sel == 0
    cmp p12, p10        // wrap if bucket == buckets
    b.eq    3f
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    b   1b          // loop

LLookupEnd$1:
LLookupRecover$1:
3:  // double wrap
    JumpMiss $0

.endmacro
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市浪册,隨后出現(xiàn)的幾起案子扫腺,更是在濱河造成了極大的恐慌,老刑警劉巖议经,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件斧账,死亡現(xiàn)場離奇詭異,居然都是意外死亡煞肾,警方通過查閱死者的電腦和手機咧织,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來籍救,“玉大人习绢,你說我怎么就攤上這事◎迹” “怎么了闪萄?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長奇颠。 經(jīng)常有香客問我败去,道長,這世上最難降的妖魔是什么烈拒? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任圆裕,我火速辦了婚禮,結(jié)果婚禮上荆几,老公的妹妹穿的比我還像新娘吓妆。我一直安慰自己,他們只是感情好吨铸,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布行拢。 她就那樣靜靜地躺著,像睡著了一般诞吱。 火紅的嫁衣襯著肌膚如雪舟奠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天房维,我揣著相機與錄音沼瘫,去河邊找鬼。 笑死握巢,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的松却。 我是一名探鬼主播暴浦,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼歌焦!你這毒婦竟也來了飞几?” 一聲冷哼從身側(cè)響起独撇,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎纷铣,沒想到半個月后卵史,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搜立,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年啄踊,在試婚紗的時候發(fā)現(xiàn)自己被綠了忧设。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颠通。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖顿锰,靈堂內(nèi)的尸體忽然破棺而出谨垃,到底是詐尸還是另有隱情,我是刑警寧澤乘客,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站淀歇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏浪默。R本人自食惡果不足惜牡直,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一纳决、第九天 我趴在偏房一處隱蔽的房頂上張望碰逸。 院中可真熱鬧,春花似錦阔加、人聲如沸饵史。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胳喷。三九已至湃番,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吭露,已是汗流浹背吠撮。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留讲竿,地道東北人泥兰。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像题禀,于是被迫代替她去往敵國和親鞋诗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

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