Runtime 03 - objc_msgSend、super
Objective-C 的消息機(jī)制
- Objective-C 中的方法調(diào)用其實(shí)都是被編譯器轉(zhuǎn)換成了對 objc_msgSend 函數(shù)的調(diào)用必逆,給 receiver(方法調(diào)用者)發(fā)送了一條消息(selector:方法名)怠堪。
- objc_msgSend 函數(shù)底層有 3 大階段:
- 消息發(fā)送(當(dāng)前類韧献、父類中查找)。
- 動態(tài)方法解析研叫。
- 消息轉(zhuǎn)發(fā)锤窑。
> objc_msgSend 源碼解讀順序
> 1. objc-msg-arm64.s
> - ENTRY _objc_msgSend // _objc_msgSend 函數(shù)入口
> - b.le LNilOrTagged // 判斷 receiver 是否為 nil
> - b.eq LReturnZero // 如果 receiver 為 nil,流程結(jié)束
> - CacheLookup NORMAL // 從緩存中查找方法嚷炉,進(jìn)入 CacheLookup
> - .macro CacheLookup
> - CacheHit $0 // 找到了方法渊啰,進(jìn)入 CacheHit
> - CheckMiss $0 // 沒有找到方法,進(jìn)入 CheckMiss
> - .macro CacheHit
> - .if $0 == NORMAL // 如果參數(shù)為 NORMAL申屹,直接調(diào)用
> - MESSENGER_END_FAST
> - br x17 // 調(diào)用方法
> - .macro CheckMiss
> - .elseif $0 == NORMAL // 調(diào)用 __objc_msgSend_uncached
> - cbz x9, __objc_msgSend_uncached
> - STATIC_ENTRY __objc_msgSend_uncached
> - MethodTableLookUp // 從方法列表中查找
> - br x17 // 調(diào)用方法
> - .macro MethodTableLookup
> - bl __class_lookupMethodAndLoadCache3 // 調(diào)用 C++ 函數(shù)
> - mov x17, x0 // 將方法返回 __objc_msgSend_uncached
> 2. objc-runtime-new.mm
> - _class_lookupMethodAndLoadCache3
> - return lookUpImpOrForward
> - lookUpImpOrForward
> - cache_getImp // 從 reveiverClass 的緩存中查找
> - return imp // 找到方法后返回 IMP
> - getMethodNoSuper_nolock // 從 reveiverClass 的方法列表中查找
> - search_method_list // 從方法列表中查找
> - log_and_fill_cache // 找到方法進(jìn)行緩存
> - return imp // 返回 IMP
> - for (Class curClass = cls->superclass;
> curClass != nil;
> curClass = curClass->superclass) // 從 superClass 中查找
> - cache_getImp // 從 superClass 的緩存中查找
> - log_and_fill_cache // 找到方法緩存緩存到 reveiverClass
> - return imp // 返回 IMP
> - getMethodNoSuper_nolock // 從 superClass 的方法列表中查找
> - search_method_list // 從方法列表中查找
> - log_and_fill_cache // 找到方法緩存緩存到 reveiverClass
> - return imp // 返回 IMP
> - _class_resolveInstanceMethod // 沒有找到方法绘证,進(jìn)行動態(tài)方法解析
> - if (!cls->isMetaClass())
> - _class_resolveInstanceMethod // 動態(tài)解析實(shí)例方法
> - else
> - _class_resolveClassMethod // 動態(tài)解析類方法
> - if (!lookUpImpOrNil()) // 類方法解析失敗會嘗試解析實(shí)例方法
> - _class_resolveInstanceMethod
> - return (IMP)_objc_msgForward_impcache // 動態(tài)方法解析失敗,嘗試消息轉(zhuǎn)發(fā)
>
> 3. objc-msg-arm64.s
> - STATIC_ENTRY __objc_msgForward_impcache // 從 C++ 函數(shù)調(diào)用到這里
> - ENTRY __objc_msgForward // 嘗試消息轉(zhuǎn)發(fā)
>
> 4. Core Foundation
> - `__forwarding__` (不開源)
objc_msgSend 01 - 消息發(fā)送
-
objc_msgSend:
- 函數(shù)簽名:
objc_msgSend(void /* id self, SEL op, ... */)
- 我們在 Objective-C 中的方法調(diào)用在編譯階段會被轉(zhuǎn)換為對 objc_msgSend 函數(shù)的調(diào)用哗讥。
<br />
例如我們編寫一段下面的代碼:
[instance instanceFunc];
在編譯階段會被轉(zhuǎn)換為:
objc_msgSend(instance, sel_registerName("instanceFunc"));
- 函數(shù)簽名:
如果是從 class_rw_t 中查找方法:
- 已經(jīng)排序的嚷那,二分查找。
- 沒有排序的杆煞,遍歷查找魏宽。
receiver 通過 isa 指針找到 receiverClass。
receiverClass 通過 superclass 指針找到 superClass决乎。
objc_msgSend 02 - 動態(tài)方法解析
開發(fā)者通過可以實(shí)現(xiàn)以下方法队询,來動態(tài)添加方法實(shí)現(xiàn):
- +resolveInstanceMethod:
- +resolveClassMethod:
動態(tài)解析過后,會重新走 “消息發(fā)送” 的流程
- 從 “在 receiverClass 的 cache 中查找方法“ 這一步開始執(zhí)行构诚。
動態(tài)添加方法示例:
objc_msgSend 03 - 消息轉(zhuǎn)發(fā)
開發(fā)者可以在 forwardInvocation: 方法中自定義任何邏輯蚌斩。
以上方法都有對象方法、類方法兩個(gè)版本范嘱。
生成 NSMethodSignature
NSMethodSignature *signature = [NSMethodSignature signatureWithObjeCTypes:"i@:ii"];
MyClass *instance = [[MyClass alloc] init];
SEL sel = @selector(myFunc:param2:)
NSMethodSignature *signature = [instance methodSignatureForSelector:sel];
super
- 用 clang -rewrite-objc 指令轉(zhuǎn)換后的 C++ 代碼看送膳,super 調(diào)用,底層會轉(zhuǎn)換為對 objc_msgSendSuper 函數(shù)的調(diào)用丑蛤。
- objc_msgSendSuper 函數(shù)接收兩個(gè)參數(shù):
-
objc_super2:
struct __rw_objc_super { struct objc_object *object; // 消息接受者 struct objc_object *superClass; // receiver 的 superclass };
SEL:
消息(要調(diào)用的方法)叠聋。
-
- objc_msgSendSuper 函數(shù)接收兩個(gè)參數(shù):
- 通過 LLVM 中間代碼、打斷點(diǎn)或 objc 源碼看盏阶,底層是對 objc_msgSendSuper2 匯編函數(shù)的調(diào)用晒奕。
- objc_msgSendSuper2 函數(shù)接收兩個(gè)參數(shù):
-
objc_super2:
struct objc_super2 { id object; // 消息接受者 Class current_class; // receiver 的 Class 對象闻书,通過 current_class->supperclass 找到父類的 Class 對象 };
SEL:
消息(要調(diào)用的方法)名斟。
-
- objc_msgSendSuper2 函數(shù)接收兩個(gè)參數(shù):
轉(zhuǎn)換后的 C++ 代碼作為參考,實(shí)際在底層調(diào)用的是 objc_msgSendSuper2 函數(shù)魄眉。
LLVM 中間代碼
- Objective-C 被轉(zhuǎn)換為機(jī)器碼之前砰盐,會被 LLVM 編譯器轉(zhuǎn)換為中間代碼(Intermediate Representation)。
- 生成 LLVM 中間代碼的指令為:
clang -emit-llvm -S source.m
- 語法簡介(官方文檔):
- @ - 全局變量
- % - 局部變量
- alloca - 在當(dāng)前執(zhí)行的函數(shù)的堆棧幀中分配內(nèi)存,當(dāng)該函數(shù)返回到其調(diào)用者時(shí),將自動釋放內(nèi)存
- i32 - 32 位 4 字節(jié)的整數(shù)
- align - 對齊
- load - 讀出种樱,store - 寫入
- icmp - 兩個(gè)整數(shù)值比較沦疾,返回布爾值
- br - 選擇分支推掸,根據(jù)條件來轉(zhuǎn)向 label罪佳,不根據(jù)條件跳轉(zhuǎn)的話類似 goto
- label - 代碼標(biāo)簽
- call - 調(diào)用函數(shù)