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.调用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 -> 内容

isa流程图.png

objc_msgSend流程分析.png

对象 - ISA - 方法(类) - cache_t - method_list(bits)

伪代码实现

[person sayHello] -> imp (cache -> bucket (sel imp))
//获取当前的对象
id person = 0x100000
//获取isa
isa_t isa = 0x000000
//isa -> class -> cache
cache_t cache = isa + 16字节

//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个字节的数据,因为sel和imp分别占用8字节

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

/*
 //伪代码实现
 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字节
//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字节)
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;//跳出循环即找到缓存中的方法,返回bucket.imp
goto CheckMiss:CheckMiss(normal)

消息发送到汇编之后,进入入口函数ENTRY,之后如何找到当前的类?

ENTRY _objc_msgSend,之后通过ISA找到类

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

查看GetClassFromIsa_p16,$0表示当前对象传过来的第一个参数为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,则向前继续查找,进行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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容