參考:
Objective-C Runtime Programming Guide
深入Objective-C的動態特性
Objective-C Runtime 運行時之一六:類與對象拾遺
Objective-C Runtime 1小時入門教程
本節將會講述iOS是如何在系列一中的數據結構的基礎上構建消息機制(消息發送到處理的全過程)的,簡單來說就是[object|class message];
之類的代碼背后發生的一切。
1. 類層次搜索
第一步 - 編譯器轉化
通過終端命令clang -rewrite-objc xxx.m可以看到xxx.m編譯后的xxx.cpp
(C++文件),比對.m文件和.cpp文件,你會發現方括號形式的方法調用基于返回類型的不同被編譯器轉化成(objc_msgSend系列函數中的某一個)的調用。
通過clang方法也可以分析block的實現,傳送門:談Objective-C block的實現、iOS中block實現的探究
// OC形式:
[receiver messageWithArgs:arg1 and:arg2 …];
// C語言函數及參數說明:
// ? receiver => 消息接收者,類型為id,通過其isa指針找到指定類的結構
// ? selector => 方法選擇器,類型為SEL,用于在類結構的方法分發表中搜索指定名字的方法實現/地址
objc_msgSend(receiver, selector, arg1, arg2, ...)
隱藏參數
在方法的實現中(OC代碼的花括號內)有兩個隱藏參數可用:self
(receiver)和_cmd
(selector)
注意:
1、在實例方法中,self表示對象;在類方法中,self表示元類對象(即類)。
2、super關鍵字實際上會被轉化成一個objc_super類型的結構體,其值為{self, self.superclass}。
struct objc_super { id receiver; Class class; };
這也意味著在子類都沒有重寫class方法時,[self class];
和[super class];
最終調用的都是NSObject的class方法實現,而接收者都是self,所以兩者返回都的都是self.class;要獲取超類,正確的方法是使用[self superclass];
。
第二步 - 追蹤繼承體系
通過objc_object/objc_class的isa
指針,沿著繼承體系在每一個objc_class結構體中:
1、在 cache
中查找指定SEL的實現,失敗轉2
2、在objc_method_list
中查找指定SEL的實現
獲取方法的地址
如果同一個方法實現你需要調用一萬次,那么通過NSObject的-methodForSelector:方法繞過動態綁定直接獲取方法的實現會提高性能(1萬次[a msg] => 一次-methodForSelector:+1萬次IMP(a, msg, …)),因為減少了方法實現的搜索次數
setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
setter(target, @selector(setFilled:), YES);
2 消息轉發
如果一直到root class都沒有定位到SEL的實現,那么轉入消息轉發過程。
步驟一:動態解析
通過實現resolveInstanceMethod:/resolveClassMethod:方法,我們有機會為該未知消息(SEL)新增一個“處理方法”(IMP)。
- 這意味著在消息轉發前,你有機會通過class_addMethod給類動態添加一些方法
- 實際上返回值YES/NO無關緊要,只要你在resovle過程中新增過方法,就會觸發
class_getMethodImplementation
,其作用相當于重新啟動一次消息發送過程。
步驟二:備用接收者
通過實現** -forwardingTargetForSelector: 方法將消息(SEL)直接轉發給另一個對象(備用接收者),也就是在另一個對象**(不能是nil或self)上重啟消息發送過程。
步驟三:完整轉發
- 通過實現 -methodSignatureForSelector: 提供方法簽名(即參數和返回值的類型信息)
- 可通過調用其他類的
+instanceMethodSignatureForSelector:
方法或其他對象的-methodSignatureForSelector:
方法提供 - 也可通過 +signatureWithObjCTypes: 自行生成
- 生成的簽名將和原始消息一起打包到一個NSInvocation對象中。
- 通過操作NSInvocation對象的target、selector屬性可以方便地轉發,甚至轉發給另一個對象的另一個需要不同參數的SEL也是可以的
- 通過 -getArgument:atIndex:和-setArgument:atIndex: 可以操作方法調用傳入的參數
- 通過 -getReturnValue:和-setReturnValue: 可以直接操作方法invoke后的返回值。
- 實現** -forwardInvocation: **方法
- 通過調用 -invoke方法 重新啟動一個消息發送過程。
- 不調用invoke,吞掉這個消息(不做任何處理)
3 轉發的功能
轉發和多繼承
轉發模擬了繼承,所以可以用來為Objc程序提供類似多繼承的功能。轉發和多繼承的區別如下:
? 多繼承是將許多功能combine到一個對象中;
? 轉發則將功能分解到多個對象,并一種對消息發送者透明的方式將它們關聯起來;
代理/替代對象
場景描述:當你有一個對象,這個對象的設置由于需要處理大量數據非常耗時,所以更傾向于懶加載——在真正需要或系統空閑的時候來進行加載,這時你需要一個占位對象來使得應用的其他部分正常工作,這個占位對象的工作如下:
? 獲取關于待加載數據的描述信息
? 轉發消息時檢測對象是否創建并已加載完數據,據此決定創建對象、丟棄消息或轉發消息。
轉發和繼承
以下方法只考慮類繼承體系(不含轉發鏈);如需要對象表現得和繼承一樣,重寫它們并把轉發算法包括進來:
? -respondsToSelector: & +instancesRespondToSelector:
? -isKindOfClass: & -isMemberOfClass:
? -conformsToProtocol:
總結:
- 消息機制:繼承體系搜索 -> 消息轉發 ( 動態解析-> 快速轉發 -> 完整轉發 )
- 轉發和繼承(-respondsToSelector:等)、多繼承、代理