1. 程式人生 > 實用技巧 >OC 底層探索 09、objc_msgSend 流程簡析1

OC 底層探索 09、objc_msgSend 流程簡析1

本文對 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 快取查詢流程圖