oc底层Runtime分析

编译时和运行时

编译时 顾名思义就是正在编译的时候 .

那什么叫编译呢?就是编译器帮你把源代码翻译成机器能识别的代码 .(当然只是一般意义上这么说,实际上可能只是翻译成某个中间状态的语言.)

编译时就是简单的作一些翻译工作 ,比如检查老兄你有没有粗心写错啥关键字了啊。有词法分析语法分析之类的过程.。就像个老师检查学生的作文中有没有错别字和病句一样 .如果发现啥错误编译器就告诉你.如果你用微软的VS的话,点下build。那就开始编译,如果下面有errors或者warning信息,那都是编译器检查出来的。所谓这时的错误就叫编译时错误,这个过程中做的啥类型检查也就叫编译时类型检查,或静态类型检查(所谓静态嘛就是没把真把代码放内存中运行起来, 而只是把代码当作文本来扫描下).。所以有时一些人说编译时还分配内存啥的肯定是错误的说法。

运行时 就是代码跑起来了。被装载到内存中去了 。(你的代码保存在磁盘上没装入内存之前是个死家伙。只有跑到内存中才变成活的)。而运行时类型检查就与前面讲的编译时类型检查(或者静态类型检查)不一样。不是简单的扫描代码.而是在内存中做些操作,做些判断。)

Runtime版本

Runtime有两个版本 一个Legacy版本(早期版本) ,一个Modern版本(现行版本**)

***** 早期版本对应的编程接口:Objective-C 1.0

***** 现行版本对应的编程接口:Objective-C 2.0

***** 早期版本用于Objective-C 1.0, 32位的Mac OS X的平台上

***** 现行版本:iPhone程序和Mac OS X v10.5 及以后的系统中的 64 位程序

Runtime调用的三种方式

  1. Objective-C方式,[penson sayHello]
  2. Framework & Serivce方式,isKindOfClass
  3. Runtime API方式,class_getInstanceSize
int main(int argc, const char * argv[]) {

  @autoreleasepool {

    ELPerson *person = [ELPerson alloc];

    [person sayNB];

  }
   return 0;
}

我们转换成c++代码,看一下底层的实现

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

int main(int argc, const char * argv[]) {

  /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

    ELPerson *person = ((ELPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("ELPerson"), sel_registerName("alloc"));

    ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));

  return 0;

}

发现底层调用了objc_msgSend()方法,所以方法的本质就是消息发送

那么有了objc_msgSend()我们可不可以直接调用发送消息呢

int main(int argc, const char * argv[]) {

  @autoreleasepool {

    ELPerson *person = [ELPerson alloc];

    ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));

    NSLog(@"Hello, World!");

  }

  return 0;

}


2021-06-28 19:26:58.975392+0800 [11456:2734467] 666

2021-06-28 19:26:58.975763+0800 [11456:2734467] Hello, World!

由此可见,直接调用objc_msgSend()发送消息也是可以的

探索objc_msgSend实现

寻找objc_msgSend实现入口

我们打开源码之后,搜索objc_msgSend,找到arm64架构下面的代码,再寻找ENTRY,就是实现部分了。如下:

image

1、.s文件的后缀代表汇编实现,objc_msgSend非常常用,用汇编实现速度会更快;

2、汇编的实现入口就是ENTRY,结束标志END_ENTRY

3、objc_msgSend在不同架构下面都有相应的实现,我们这边主要看真机下面的实现(以arm64结尾的)

objc_msgSend arm64下基本实现

1、比较p0,是否为空。p0也就是消息接收者

2、判断是否支持TAGGED_POINTERS是,返回LNilOrTagged,否,返回LReturnZero

3、通过对象isa获取class存p16

4、获取完LGetIsaDoneCacheLookup流程

    ENTRY _objc_msgSend

    UNWIND _objc_msgSend, NoFrame


    cmp p0, #0          // 判断p0是否为空,p0就是消息接受者

#if SUPPORT_TAGGED_POINTERS  //__LP64__支持TAGGED_POINTERS类型

    b.le    LNilOrTagged        // (MSB tagged pointer looks negative)

#else

    b.eq    LReturnZero

#endif

    ldr p13, [x0]       // p13 = isa

    GetClassFromIsa_p16 p13, 1, x0  // p16 = class

LGetIsaDone://符号标记,拿到isa之后继续后面操作

    // 调用CacheLookup传递三个参数NORMAL, _objc_msgSend, __objc_msgSend_uncached

    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached



\#if SUPPORT_TAGGED_POINTERS

LNilOrTagged:

    b.eq    LReturnZero     // nil check

    GetTaggedClass

    b   LGetIsaDone

// SUPPORT_TAGGED_POINTERS

\#endif



LReturnZero:

    // x0 is already zero

    mov x1, #0

    movi    d0, #0

    movi    d1, #0

    movi    d2, #0

    movi    d3, #0

    ret
 //结束标志
    END_ENTRY _objc_msgSend

5、通过内存平移获取得到p11

6、通过掩码运算获取到bucketsand p10, p11, #0x0000fffffffffffe

7、通过逻辑右移计算哈希函数得到_cmd & mask

8、通过bucket结构体得到(imp,sel)= *bucket

9、判断查询的sel和_cmd是否相等

9.1、通过向前查找的方式递归查找{imp, sel} = *bucket--

9.2、相等的时候命中CacheHit

9.3、最后还是找不到MissLabelDynamicif (sel == 0) goto Miss;

//参数拿过来 NORMAL, _objc_msgSend, __objc_msgSend_uncached,MissLabelConstant
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
//x16的值放入x15
    mov x15, x16            // stash the original isa

LLookupStart\Function:

// p1 = SEL, p16 = isa

\#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS//模拟器或者macOS

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

    lsr p11, p10, #48           // p11 = mask

    and p10, p10, #0xffffffffffff   // p10 = buckets

    and w12, w1, w11            // x12 = _cmd & mask

\#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16//arm64真机
 //#define CACHE            (2 * __SIZEOF_POINTER__)__SIZEOF_POINTER__=指针大小=8
 //[x16, #CACHE] = isa+ 0x10 = _bucketsAndMaybeMask的内存地址//然后放入到p11中
    ldr p11, [x16, #CACHE]          // p11 = mask|buckets
//arm64下面是1
\#if CONFIG_USE_PREOPT_CACHES
//A12以后的芯片
\#if __has_feature(ptrauth_calls)
  // 63..60: hash_mask_shift
   // 59..55: hash_shift
   // 54.. 1: buckets ptr + auth
   //      0: always 1

//测试位不为0时,跳转
    tbnz    p11, #0, LLookupPreopt\Function

and p10, p11, #0x0000ffffffffffff   // p10 = buckets

\#else
 //p11 & #0x0000fffffffffffe = buckets //获取第1位到第47位
    // 63..53: hash_mask
    // 52..48: hash_shift
    // 47.. 1: buckets ptr
    //      0: always 1
 
    and p10, p11, #0x0000fffffffffffe   // p10 = buckets
//测试位不为0时,跳转
    tbnz    p11, #0, LLookupPreopt\Function

\#endif
 // p12 = p1 ^(p1 >> 7) = _cmd ^ (_cmd >> 7)
    eor p12, p1, p1, LSR #7
//p11 >> 48 = mask 
    and p12, p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask

\#else
 //p11 = caceh & #0x0000ffffffffffff = buckets = p10
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
//p11 >> 48 = mask & p1(sel) = p12(index)
    and p12, p1, p11, LSR #48       // x12 = _cmd & mask

\#endif // CONFIG_USE_PREOPT_CACHES

\#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4

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

    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

//p10 = buckets
//mask & p1(sel) = p12(index)
//PTRSHIFT = 3
//mask & _cmd << 4 相当于地址转下标---->相当于b[i]= b 平移i个单位
//p13就是当前要查找的bucket
    add p13, p10, p12, LSL #(1+PTRSHIFT)

                        // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))



                        // do {
//[x13]=*bucket--
//取到bucket里面的imp和sel
//分别存到p17和p9里面
1:  ldp p17, p9, [x13], #-BUCKET_SIZE   //   {imp, sel} = *bucket--
//比较sel 和传进来的_cmd
    cmp p9, p1              //   if (sel != _cmd) {

    b.ne    3f              //     scan more

                        //   } else {
//缓存命中
2:  CacheHit \Mode              // hit:  call or return imp

                        //   }
//判断p9是否为空->MissLabelDynamic
3:  cbz p9, \MissLabelDynamic       //   if (sel == 0) goto Miss;

    cmp p13, p10            // } while (bucket >= buckets)

    b.hs    1b



    // wrap-around:

    //  p10 = first bucket

    //  p11 = mask (and maybe other bits on LP64)

    //  p12 = _cmd & mask

    //

\#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS

    add p13, p10, w11, UXTW #(1+PTRSHIFT)

                        // p13 = buckets + (mask << 1+PTRSHIFT)

\#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16

    add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))

                    // p13 = buckets + (mask << 1+PTRSHIFT)

                        // see comment about maskZeroBits

\#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4

    add p13, p10, p11, LSL #(1+PTRSHIFT)

                        // p13 = buckets + (mask << 1+PTRSHIFT)

\#else

\#error Unsupported cache mask storage for ARM64.

\#endif

    add p12, p10, p12, LSL #(1+PTRSHIFT)

                        // p12 = first probed bucket



                        // do {

4:  ldp p17, p9, [x13], #-BUCKET_SIZE   //   {imp, sel} = *bucket--

    cmp p9, p1              //   if (sel == _cmd)

    b.eq    2b              //     goto hit

    cmp p9, #0              // } while (sel != 0 &&

    ccmp    p13, p12, #0, ne        //   bucket > first_probed)

    b.hi    4b



LLookupEnd\Function:

LLookupRecover\Function:

    b   \MissLabelDynamic



\#if CONFIG_USE_PREOPT_CACHES

\#if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16

\#error config unsupported

\#endif

LLookupPreopt\Function:

\#if __has_feature(ptrauth_calls)

    and p10, p11, #0x007ffffffffffffe   // p10 = buckets

    autdb   x10, x16            // auth as early as possible

\#endif



    // x12 = (_cmd - first_shared_cache_sel)

    adrp    x9, _MagicSelRef@PAGE

    ldr p9, [x9, _MagicSelRef@PAGEOFF]

    sub p12, p1, p9



    // w9 = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)

\#if __has_feature(ptrauth_calls)

    // bits 63..60 of x11 are the number of bits in hash_mask

    // bits 59..55 of x11 is hash_shift



    lsr x17, x11, #55           // w17 = (hash_shift, ...)

    lsr w9, w12, w17            // >>= shift



    lsr x17, x11, #60           // w17 = mask_bits

    mov x11, #0x7fff

    lsr x11, x11, x17           // p11 = mask (0x7fff >> mask_bits)

    and x9, x9, x11         // &= mask

\#else

    // bits 63..53 of x11 is hash_mask

    // bits 52..48 of x11 is hash_shift

    lsr x17, x11, #48           // w17 = (hash_shift, hash_mask)

    lsr w9, w12, w17            // >>= shift

    and x9, x9, x11, LSR #53        // &= mask

\#endif



    ldr x17, [x10, x9, LSL #3]      // x17 == sel_offs | (imp_offs << 32)

    cmp x12, w17, uxtw



.if \Mode == GETIMP

    b.ne    \MissLabelConstant      // cache miss

    sub x0, x16, x17, LSR #32       // imp = isa - imp_offs

    SignAsImp x0

    ret

.else

    b.ne    5f              // cache miss

    sub x17, x16, x17, LSR #32      // imp = isa - imp_offs

.if \Mode == NORMAL

    br  x17

.elseif \Mode == LOOKUP

    orr x16, x16, #3 // for instrumentation, note that we hit a constant cache

    SignAsImp x17

    ret

.else

.abort unhandled mode \Mode

.endif


5:  ldursw  x9, [x10, #-8]          // offset -8 is the fallback offset

    add x16, x16, x9            // compute the fallback isa

    b   LLookupStart\Function       // lookup again with a new isa

.endif

\#endif // CONFIG_USE_PREOPT_CACHES


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

推荐阅读更多精彩内容