運(yùn)行時(shí)理解
通過這張圖片我們可以看到姆怪,我們平時(shí)調(diào)用
oc
方法其實(shí)本質(zhì)就是調(diào)用 runtime
的 api
杯道,就是發(fā)消息夹厌。那么我們平常的 oc
方法調(diào)用豹爹,在底層又是如何實(shí)現(xiàn)的呢?
#import <Foundation/Foundation.h>
#import <objc/message.h>
@interface CXPerson : NSObject
- (void)sayHello;
@end
@implementation CXPerson
- (void)sayHello {
NSLog(@"CXPerson - sayHello");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
CXPerson *person = [CXPerson alloc];
[person sayHello];
}
return 0;
}
我們先建立一個(gè)工程矛纹,在 main.m
文件定義一個(gè) CXPerson
類臂聋,并調(diào)用 sayHello
方法。
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
CXPerson *person = ((CXPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CXPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));
}
return 0;
}
然后打開終端或南,cd 到 main.m 所在文件目錄孩等,然后輸入 clang -rewrite-objc main.m -o main.cpp 命令,最后會(huì)看到生成了一個(gè) main.cpp 文件采够。 然后找到 main
函數(shù)的實(shí)現(xiàn)代碼肄方。可以看到 alloc
跟 sayHello
方法的調(diào)用在底層都會(huì)轉(zhuǎn)為 objc_msgSend
的調(diào)用形式蹬癌。
通過代碼可以看到消息的發(fā)送需要兩個(gè)參數(shù) objc_msgSend(消息接受者, 消息的主題(sel + 參數(shù)))
权她,消息接收者跟消息主題。那么我們是否能直接調(diào)用底層的消息發(fā)送方法呢逝薪?這里我們來試一下隅要。
這里可以看到,我們自己直接調(diào)用
objc_msgSend
方法也是可以的董济。
objc_msgSend
源碼探究
這里我們來看一下
objc_msgSend
方法再源碼中的實(shí)現(xiàn)步清,全局搜索 objc_msgSend
我們可以看到,不同架構(gòu)下 objc_msgSend
對(duì)應(yīng)的匯編代碼虏肾。因?yàn)槲覀冋鏅C(jī)常用的是 arm64
架構(gòu)廓啊,這里我們就分析一下 arm64
架構(gòu)下的匯編實(shí)現(xiàn)。
-
ENTRY _objc_msgSend
(進(jìn)入到 objc_msgSend )
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
// p0代表消息接受者的地址询微,這里判斷消息接收者是否存在
cmp p0, #0 // nil check and tagged pointer check
// 如果消息接受者不存在崖瞭,就會(huì)走到這里。這這里也會(huì)有判斷
#if SUPPORT_TAGGED_POINTERS
// 如果為SUPPORT_TAGGED_POINTERS類型走到這里
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
// 不為SUPPORT_TAGGED_POINTERS類型走到這里
b.eq LReturnZero
// 如果消息接收者存在就會(huì)走到這里
#endif
ldr p13, [x0] // p13 = isa (消息接收者的首地址)
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
// 這個(gè)通過消息接收者 receviece 獲取 class 的原因是因?yàn)榉椒ù嬖?cache 里面撑毛,而 cache 是類結(jié)構(gòu)體的屬性书聚。cache 中有 cache_getIMP方法。通過 sel 獲取 imp藻雌。
LGetIsaDone:
// calls imp or 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
這一步主要是判斷消息接受者是否存在雌续,如果存在的話就把 isa
賦值給 p13
,并作為參數(shù)帶入到 GetClassFromIsa_p16
方法胯杭。
-
.macro GetClassFromIsa_p16 src, needs_auth, auth_address
(通過 isa 獲取 class)
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
#if SUPPORT_INDEXED_ISA
// Indexed isa
mov p16, \src // 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__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
mov p16, \src
.else
// 64-bit packed isa
// and $0, $1, #ISA_MASK $1就是 p13,就是 isa驯杜,與上ISA_MASK得到 class,并賦值給 p16
ExtractISA p16, \src, \auth_address
.endif
#else
// 32-bit raw isa
mov p16, \src
#endif
這一步主要是通過 isa
獲取 class
做个,因?yàn)?cache
是類結(jié)構(gòu)體的一個(gè)屬性鸽心。而 chache_getIMP
存在于 cache
中滚局。
全局搜索可以看到
ExtractISA
的實(shí)現(xiàn),代表 $1
與上 ISA_MASK
并賦值給 $0
顽频。
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
// 把 `p16` 移動(dòng)到 `x15`
mov x15, x16 // stash the original isa
// 開始進(jìn)入objc_msgSend流程
LLookupStart\Function:
// p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
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 結(jié)果會(huì)走到這里
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
// 全局搜索CONFIG_USE_PREOPT_CACHES(#if defined(__arm64__) && TARGET_OS_IOS && !TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST#define CONFIG_USE_PREOPT_CACHES 1) 得到CONFIG_USE_PREOPT_CACHES等于 1藤肢,所以走到這里
#if __has_feature(ptrauth_calls)
tbnz p11, #0, LLookupPreopt\Function
and p10, p11, #0x0000ffffffffffff // p10 = buckets
#else
//p11` 與上 `0x0000fffffffffffe` 得到 `buckets` 并賦值給 `p10
and p10, p11, #0x0000fffffffffffe // p10 = buckets
//p11跟 0 相比較,如果不為零就跳轉(zhuǎn)到到 LLookupPreopt
tbnz p11, #0, LLookupPreopt\Function
#endif
// 如果為零會(huì)走到這里糯景。p1 右移7 個(gè)位置賦值給 x12
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
// 如果模擬器會(huì)走到這里嘁圈,p11為 cache,與上0x0000ffffffffffff得到 buckets 并賦值給 p10
and p10, p11, #0x0000ffffffffffff // p10 = buckets
//p11右移動(dòng) 48 位就會(huì)得到 mask 的值
// p1(_cmd) & mask -> index -> 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
// p11 cache -> p10 = buckets
// p1(_cmd) & mask = index -> p12
// 全局搜索 PTRSHIFT 等于 3 #define PTRSHIFT 3
// (_cmd & mask) << (1+PTRSHIFT) -> (_cmd & mask) << 4
// buckets + 內(nèi)存平移(1蟀淮,2最住,3,4) = b[i]
// p13 當(dāng)前查找的 bucket
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// do {
// * bucket-- p17, p9同時(shí)存入bucket--
// 拿到相應(yīng)的bucket里面的東西 imp(p17) sel(p9)
// 查到的 sel 跟我們要查的 sel 進(jìn)行比較怠惶,如果一樣涨缚,就會(huì)走到 2 里面,不等于就會(huì)走到 3 里面策治,其實(shí)就是循環(huán)查找
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
cmp p9, p1 // if (sel != _cmd) {
b.ne 3f // scan more
// } else {
2: CacheHit \Mode // hit: call or return imp
// }
3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
cmp p13, p10 // } while (bucket >= buckets)
b.hs 1b
調(diào)用 CacheLookup
方法并帶入 NORMAL
, _objc_msgSend
, __objc_msgSend_uncached
這幾個(gè)參數(shù)仗岖。
-
ldr p11, [x16, #CACHE]
把x16
的地址平移CACHE
大小。 -
#define CACHE (2 * __SIZEOF_POINTER__)
全局搜索#define CACHE
可以得到CACHE
是兩個(gè)指針的大小為 16個(gè)字節(jié)览妖。 -
[x16, #CACHE] -> p11 -> cache_t
所以p11
就是等于cache_t
轧拄。 CONFIG_USE_PREOPT_CACHES
#if defined(__arm64__) && TARGET_OS_IOS && !TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST
#define CONFIG_USE_PREOPT_CACHES 1
全局搜索 CONFIG_USE_PREOPT_CACHES
,得到 CONFIG_USE_PREOPT_CACHES
等于 1讽膏。
//p11` 與上 `0x0000fffffffffffe` 得到 `buckets` 并賦值給 `p10
and p10, p11, #0x0000fffffffffffe // p10 = buckets
//p11跟 0 相比較檩电,如果不為零就跳轉(zhuǎn)到到 LLookupPreopt
tbnz p11, #0, LLookupPreopt\Function
LLookupPreopt\Function
CacheHit
.macro CacheHit
.if $0 == NORMAL
// 如果 $0 等于 NORMAL就會(huì)走到TailCallCachedImp
TailCallCachedImp x17, x10, x1, x16 // authenticate and
TailCallCachedImp
//CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro TailCallCachedImp
// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
// $0(x17) ^ $3(isa) = 編碼(imp)
eor $0, $0, $3
// 跳轉(zhuǎn)到$0(imp),就是執(zhí)行 imp 方法
br $0
最后總結(jié)
objc_msgSend(recevier, _cmd) sel -> imp
- 判斷
recevier
是否存在 - 通過
recevier
找到isa
找到class
(p16) -
class
內(nèi)存平移得到cache
(bucket mask) - bucket掩碼 -> bucket
- mask掩碼 -> mask
- insert哈希函數(shù)(mask_t)(value & mask);
- 第一次查找 index
- bucket + index -> 整個(gè)緩存里的第幾個(gè) bucket
- bucket{imp, sel}
- sel 與我們查找的 _cmd比較,如果相等 -> cacheHit(緩存命中) -> imp ^ class(isa) = imp -> (br)調(diào)用 imp
- 如果不相等 bucket-- ->再次平移 -> 找到 bucket 再次比較
- 死循環(huán) 遍歷查找
- 一直找不到就會(huì)走的
__objc_msgSend_uncached
(執(zhí)行CacheLookup方法帶入的第三個(gè)參數(shù))
未完待續(xù)府树。俐末。。