本文章將記錄Objective-C中消息傳遞和轉(zhuǎn)發(fā)機(jī)制济蝉、Method Swizzling的相關(guān)資料,如有錯(cuò)誤歡迎指出~
Objective-C 本質(zhì)上是一種基于 C 語(yǔ)言的領(lǐng)域特定語(yǔ)言澜术。C 語(yǔ)言是一門靜態(tài)語(yǔ)言,其在編譯時(shí)決定調(diào)用哪個(gè)函數(shù)猬腰。而 Objective-C 則是一門動(dòng)態(tài)語(yǔ)言瘪板,其在編譯時(shí)不能決定最終執(zhí)行時(shí)調(diào)用哪個(gè)函數(shù)(Objective-C 中函數(shù)調(diào)用稱為消息傳遞)。Objective-C 的這種動(dòng)態(tài)綁定機(jī)制正是通過(guò) runtime 這樣一個(gè)中間層實(shí)現(xiàn)的漆诽。
消息傳遞(方法調(diào)用)
在 Objective-C 中,消息直到運(yùn)行時(shí)才綁定到方法實(shí)現(xiàn)上锣枝。編譯器會(huì)將消息表達(dá)式轉(zhuǎn)化為一個(gè)消息函數(shù)的調(diào)用厢拭。
OC中的消息表達(dá)式如下(方法調(diào)用)
id returnValue = [someObject messageName:parameter];
編譯器看到這條消息會(huì)轉(zhuǎn)換成一條標(biāo)準(zhǔn)的 C 語(yǔ)言函數(shù)調(diào)用
id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);
我們可以看到轉(zhuǎn)換中,使用到了objc_msgSend
函數(shù)撇叁,這個(gè)函數(shù)將消息接收者和方法名作為主要參數(shù)供鸠,如下所示:
objc_msgSend(receiver, selector) // 不帶參數(shù)
objc_msgSend(receiver, selector, arg1, arg2,...) // 帶參數(shù)
objc_msgSend
通過(guò)以下幾個(gè)步驟實(shí)現(xiàn)了動(dòng)態(tài)綁定機(jī)制。
- 首先陨闹,獲取
selector
指向的方法實(shí)現(xiàn)楞捂。由于相同的方法可能在不同的類中有著不同的實(shí)現(xiàn),因此根據(jù)receiver
所屬的類進(jìn)行判斷趋厉。 - 其次寨闹,傳遞
receiver
對(duì)象、方法指定的參數(shù)來(lái)調(diào)用方法實(shí)現(xiàn)君账。 - 最后繁堡,返回方法實(shí)現(xiàn)的返回值。
消息傳遞的關(guān)鍵在于【iOS面試糧食】Runtime—實(shí)例對(duì)象乡数、類對(duì)象椭蹄、元類對(duì)象記錄過(guò)的 objc_class
結(jié)構(gòu)體,其有兩個(gè)關(guān)鍵的字段:
-
isa
:指向父類的指針 -
methodLists
: 類的方法分發(fā)表(dispatch table
)
當(dāng)創(chuàng)建一個(gè)新對(duì)象時(shí)净赴,先為其分配內(nèi)存绳矩,并初始化其成員變量。其中 isa
指針也會(huì)被初始化玖翅,讓對(duì)象可以訪問(wèn)類及類的繼承鏈翼馆。
下圖所示為消息傳遞過(guò)程的示意圖。
- 當(dāng)消息傳遞給一個(gè)對(duì)象時(shí)烧栋,首先從運(yùn)行時(shí)系統(tǒng)緩存
objc_cache
中進(jìn)行查找写妥。如果找到,則執(zhí)行审姓。否則珍特,繼續(xù)執(zhí)行下面步驟。 -
objc_msgSend
通過(guò)對(duì)象的isa
指針獲取到類的結(jié)構(gòu)體魔吐,然后在方法分發(fā)表methodLists
中查找方法的selector
扎筒。如果未找到莱找,將沿著類的isa
找到其父類,并在父類的分發(fā)表methodLists
中繼續(xù)查找嗜桌。 - 以此類推奥溺,一直沿著類的繼承鏈追溯至
NSObject
類。一旦找到selector
骨宠,傳入相應(yīng)的參數(shù)來(lái)執(zhí)行方法的具體實(shí)現(xiàn)浮定,并將該方法加入緩存objc_cache
。如果最后仍然沒(méi)有找到selector
层亿,則會(huì)進(jìn)入消息轉(zhuǎn)發(fā)流程
消息轉(zhuǎn)發(fā)
當(dāng)一個(gè)對(duì)象能接收一個(gè)消息時(shí)桦卒,會(huì)走正常的消息傳遞流程。當(dāng)一個(gè)對(duì)象無(wú)法接收某一消息時(shí)匿又,會(huì)發(fā)生什么呢方灾?
- 默認(rèn)情況下,如果以
[object message]
的形式調(diào)用方法碌更,如果object
無(wú)法響應(yīng)message
消息時(shí)裕偿,編譯器會(huì)報(bào)錯(cuò)。 - 如果是以
performSeletor:
的形式調(diào)用方法痛单,則需要等到運(yùn)行時(shí)才能確定object
是否能接收message
消息嘿棘。如果不能,則程序崩潰桦他。
對(duì)于后者蔫巩,當(dāng)不確定一個(gè)對(duì)象是否能接收某個(gè)消息時(shí),可以調(diào)用 respondsToSelector:
來(lái)進(jìn)行判斷快压。
if ([self respondsToSelector:@selector(method)]) {
[self performSelector:@selector(method)];
}
事實(shí)上圆仔,當(dāng)一個(gè)對(duì)象無(wú)法接收某一消息時(shí),就會(huì)啟動(dòng)所謂“消息轉(zhuǎn)發(fā)(message forwarding)”機(jī)制蔫劣。通過(guò)消息轉(zhuǎn)發(fā)機(jī)制坪郭,我們可以告訴對(duì)象如何處理未知的消息。
消息轉(zhuǎn)發(fā)機(jī)制大致可分為三個(gè)步驟:
- 動(dòng)態(tài)方法解析(Dynamic Method Resolution)
- 備用接收者
- 完整消息轉(zhuǎn)發(fā)
下圖所示為消息轉(zhuǎn)發(fā)過(guò)程的示意圖脉幢。
動(dòng)態(tài)方法解析
這是整個(gè)消息轉(zhuǎn)發(fā)流程的第一個(gè)階段歪沃,如果在收到無(wú)法響應(yīng)的消息后,會(huì)調(diào)用所屬類的方法:
//實(shí)例對(duì)象
+ (BOOL)resolveInstanceMethod:(SEL)selector
//類對(duì)象
+ (BOOL)resolveClassMethod:(SEL)selector
其中參數(shù)selector
為未處理的方法嫌松。
返回值@return
表示能否新增一個(gè)方法來(lái)處理沪曙,一般使用@dynamic屬性來(lái)實(shí)現(xiàn):
/************** 使用 resolveInstanceMethod 實(shí)現(xiàn) @dynamic 屬性 **************/
id autoDictionaryGetter(id self, SEL _cmd);
void autoDictionarySetter(id self, SEL _cmd, id value);
+ (BOOL)resolveInstanceMethod:(SEL)selector
{
NSString *selectorString = NSStringFromSelector(selector);
if (/* selector is from a @dynamic property */)
{
if ([selectorString hasPrefix:@"set"])
{
// 添加 setter 方法
class_addMethod(self, selector, (IMP)autoDictionarySetter, "v@:@");
}
else
{
// 添加 getter 方法
class_addMethod(self, selector, (IMP)autoDictionaryGetter, "@@:");
}
return YES;
}
return [super resolveInstanceMethod:selector];
}
備援接受者
這是整個(gè)消息轉(zhuǎn)發(fā)機(jī)制的第二站,看名字就可以看出來(lái)萎羔,這是在尋找一個(gè)備用援救的接受者液走,到了這一階段,系統(tǒng)會(huì)調(diào)用這個(gè)方法:
- (id)forwardingTargetForSelector:(SEL)aSelector;
傳入?yún)?shù)aSelector
同樣為無(wú)法處理的方法。
返回值為當(dāng)前找到的備援接受者缘眶,如果沒(méi)有則返回nil嘱根,進(jìn)入下一階段。
完整的消息轉(zhuǎn)發(fā)機(jī)制
如果前兩個(gè)階段都沒(méi)有辦法處理消息巷懈,就會(huì)啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制该抒。
首先會(huì)創(chuàng)建NSInvocation
對(duì)象,把尚未處理的那條消息的全部信息細(xì)節(jié)裝在里邊顶燕,在觸發(fā)NSInvocation
對(duì)象時(shí)凑保,系統(tǒng)派發(fā)系統(tǒng)(message-dispatch system)將會(huì)把消息指派給目標(biāo)對(duì)象。這時(shí)會(huì)調(diào)用該方法:
- (void)forwardInvocation:(NSInvocation *)anInvocation;
傳入的參數(shù)anInvocation
就包含了消息的所有內(nèi)容涌攻。
如果此時(shí)還是沒(méi)辦法處理消息愉适,就會(huì)沿著繼承的順序一步一步向父類調(diào)用相同的方法,直到最后的NSObject
類中癣漆,這時(shí)候如果還沒(méi)有辦法處理消息,就會(huì)調(diào)用doesNotRecognizeSelector:
拋出異常剂买。
到此為止惠爽,消息轉(zhuǎn)發(fā)的整個(gè)流程就都結(jié)束了。
Method Swizzling
談到黑科技瞬哼,就不得不提一下Objective-C 中的 Method Swizzling 技術(shù)婚肆,它可以允許我們動(dòng)態(tài)地替換方法的實(shí)現(xiàn),實(shí)現(xiàn) Hook
功能坐慰,是一種比子類化更加靈活的“重寫”方法的方式较性。就是說(shuō)在開(kāi)發(fā)中,我們可能會(huì)遇到系統(tǒng)提供的 API 不能滿足實(shí)際需求结胀,我們希望能夠修改它以達(dá)到期望的效果赞咙。
Method Swizzling 原理
Method Swizzling 的實(shí)現(xiàn)充分利用了動(dòng)態(tài)綁定機(jī)制。
在 Objective-C 中調(diào)用方法糟港,其實(shí)是向一個(gè)對(duì)象發(fā)送消息攀操,而查找消息的唯一依據(jù)是方法名 selector
。每個(gè)類都有一個(gè)方法列表 objc_method_list
秸抚,存放著其所有的方法 objc_method
速和。
typedef struct objc_method *Method
struct objc_method{
SEL method_name OBJC2_UNAVAILABLE; // 方法名
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE; // 方法實(shí)現(xiàn)
}
每個(gè)方法 objc_method
保存了方法名(SEL
)和方法實(shí)現(xiàn)(IMP
)的映射關(guān)系。Method Swizzling 其實(shí)就是重置了 SEL
和 IMP
的映射關(guān)系剥汤。如下圖所示:
具體的應(yīng)用場(chǎng)景可以參考 iOS 開(kāi)發(fā):『Runtime』詳解(二)Method Swizzling
參考
iOS開(kāi)發(fā)-runtime-消息傳遞和轉(zhuǎn)發(fā)機(jī)制
Objective-C Runtime 消息傳遞與轉(zhuǎn)發(fā)