首页 > 其他 > 详细

OC 底层探索 09、objc_msgSend 流程简析1

时间:2020-09-23 21:04:00      阅读:48      评论:0      收藏:0      [点我收藏+]

本文对 Runtime 进行简单介绍 和对 objc_msgSend 的发消息流程中的缓存查找进行探索。

我们知道类结构中包含了很多信息:isa superclass cache bits,cache 中缓存了我们调用的方法,具体流程见OC底层探索07. 但是方法具体时间什么时候缓存的,需要继续探究。

源码 objc_cache.mm 文件,Method cache locking 中,我们可以看到在写入之前还有一个读的过程

--> objc_msgSend* / cache_getImp

技术分享图片

看到 objc_msgSend  就不免想到 runtime,我们先简单介绍下 runtime 是什么。

一、Runtime 简介

Runtime 是一个为我们OC语言开发中提供动态特性的一个

官方文档: Objective-C Runtime 

    /Objective-C Runtime Programming Guide

技术分享图片

The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work.

OC 语言将尽可能多的决策从编译时链接时延迟到运行时。只要有可能,它就动态地做事情。这意味着该语言不仅需要一个编译器,还需要一个运行时系统来执行编译后的代码。运行时系统作为Objective-C语言的一种操作系统;它使语言起作用。

提取信息点:一个提供动态特性的库,实现运行时作用。

1、Runtime 结构和调用方式:

技术分享图片

complier: 编译 --> runtime 底层所调用的并非我们写的代码,它进过了 complier 进行编译、优化。 --> LLVM

tip:编译时、链接时,一句话简介 {

编译时

  1、对我们的代码进行语法词法等分析,并给出waringerror等提示。类似一个扫描过程,将代码扫一遍;

  2、将编写的高级语言(C C++ OC等)编译成机器识别的机器语言(汇编、机器语言01等),编译得到相应的二进制目标文件

编译时并没有进行加载分配内存等操作。

链接时

  将编译得到的二进制文件和库函数等进行连接。  

运行时

  代码已被装载到内存中,已运行起来了。

} 

2、探索

通过clang 将 main.m 文件编译成 mian.cpp. mian函数编译结果如下:

技术分享图片

技术分享图片 

id objc_msgSend(id self, SEL _cmd, ...) :

  消息接受者 self;消息体 SEL. 通过查找 objc_msgSend() 源码,我们可以发现它是使用汇编实现的,这里暂时先不探究,继续当前操作。

sel_registerName("helloObj1"):

  注册一个方法。例: 上层的操作 @selector()NSSelectorFromString() 都是 SEL 类型.

直接使用 API - objc_msgSend() 进行方法调用

对代码进行修改:

技术分享图片

报错如下:

技术分享图片

修改工程配置:Building Setting --> Preprocessing --> 将 objc_msgSend call 严格检查改为 NO.

技术分享图片 

运行结果如下,通过 objc_msgSend() 正常调用了方法 ‘helloObj3‘:

技术分享图片 

OC 方法调用 --> objc_msgSend() --> sel (方法编号) --> imp (函数指针地址) --> 函数.

sel 如何找到 imp 呢? 

二、objc_msgSend 流程探索 - cache

从上面的操作,可知方法调用的本质是 发送消息,下面进行 发送消息流程的探究。

全局查找 objc_msgSend(),是通过汇编实现的

技术分享图片

使用汇编的原因:

  1、快速,方法的查找操作是很频繁的,汇编是相对底层的语言更易被机器识别,节省中间的一些编译过程。

  2、语言的动态特性,C/C++ 来编写实现的话更偏向于静态,虽然也可实现会更麻烦且慢。

下面我们通过源码 注释 和 网络 对消息查找流程进行探索。

objc_msgSend 流程分析 

消息接收者 和 sel:

消息接受者 --> 对象 --> isa --> 方法(类/元类) --> cache_t --> methodliss(bits中) 

1、主要流程源码 CacheLookup:

 1 .macro CacheLookup
 2     //
 3     // Restart protocol:
 4     //
 5     //   As soon as we‘re past the LLookupStart$1 label we may have loaded
 6     //   an invalid cache pointer or mask.
 7     //
 8     //   When task_restartable_ranges_synchronize() is called,
 9     //   (or when a signal hits us) before we‘re past LLookupEnd$1,
10     //   then our PC will be reset to LLookupRecover$1 which forcefully
11     //   jumps to the cache-miss codepath which have the following
12     //   requirements:
13     //
14     //   GETIMP:
15     //     The cache-miss is just returning NULL (setting x0 to 0)
16     //
17     //   NORMAL and LOOKUP:
18     //   - x0 contains the receiver
19     //   - x1 contains the selector
20     //   - x16 contains the isa
21     //   - other registers are set as per calling conventions
22     //
23 LLookupStart$1:
24 
25     // p1 = SEL, p16 = isa
26     ldr    p11, [x16, #CACHE]                // p11 = mask|buckets
27 
28 #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
29     and    p10, p11, #0x0000ffffffffffff    // p10 = buckets
30     and    p12, p1, p11, LSR #48        // x12 = _cmd & mask
31 #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
32     and    p10, p11, #~0xf            // p10 = buckets
33     and    p11, p11, #0xf            // p11 = maskShift
34     mov    p12, #0xffff
35     lsr    p11, p12, p11                // p11 = mask = 0xffff >> p11
36     and    p12, p1, p11                // x12 = _cmd & mask
37 #else
38 #error Unsupported cache mask storage for ARM64.
39 #endif
40 
41 
42     add    p12, p10, p12, LSL #(1+PTRSHIFT)
43                      // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) // PTRSHIFT=3
44 
45     ldp    p17, p9, [x12]        // {imp, sel} = *bucket
46 1:    cmp    p9, p1            // if (bucket->sel != _cmd)
47     b.ne    2f            //     scan more
48     CacheHit $0            // call or return imp
49     
50 2:    // not hit: p12 = not-hit bucket
51     CheckMiss $0            // miss if bucket->sel == 0
52     cmp    p12, p10        // wrap if bucket == buckets
53     b.eq    3f
54     ldp    p17, p9, [x12, #-BUCKET_SIZE]!    // {imp, sel} = *--bucket
55     b    1b            // loop
56 
57 3:    // wrap: p12 = first bucket, w11 = mask
58 #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
59     add    p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
60                     // p12 = buckets + (mask << 1+PTRSHIFT)
61 #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
62     add    p12, p12, p11, LSL #(1+PTRSHIFT)
63                     // p12 = buckets + (mask << 1+PTRSHIFT)
64 #else
65 #error Unsupported cache mask storage for ARM64.
66 #endif
67 
68     // Clone scanning loop to miss instead of hang when cache is corrupt.
69     // The slow path may detect any corruption and halt later.
70 
71     ldp    p17, p9, [x12]        // {imp, sel} = *bucket
72 1:    cmp    p9, p1            // if (bucket->sel != _cmd)
73     b.ne    2f            //     scan more
74     CacheHit $0            // call or return imp
75     
76 2:    // not hit: p12 = not-hit bucket
77     CheckMiss $0            // miss if bucket->sel == 0
78     cmp    p12, p10        // wrap if bucket == buckets
79     b.eq    3f
80     ldp    p17, p9, [x12, #-BUCKET_SIZE]!    // {imp, sel} = *--bucket
81     b    1b            // loop
82 
83 LLookupEnd$1:
84 LLookupRecover$1:
85 3:    // double wrap
86     JumpMiss $0
87 
88 .endmacro

2、objc_msgSend 缓存查找流程图

技术分享图片

 

OC 底层探索 09、objc_msgSend 流程简析1

原文:https://www.cnblogs.com/zhangzhang-y/p/13704597.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!