前言
本來打算寫一篇關(guān)于runtime的學(xué)習(xí)總結(jié),無奈長篇大論不是我的風(fēng)格,就像寫申論一樣痛苦,加之網(wǎng)上關(guān)于tuntime的文章多如牛毛,應(yīng)該也夠童子們學(xué)習(xí)的了,今天就隨便聊聊我的理解吧。
runtime是什么
對于初學(xué)者,runtime如尼斯湖水怪一樣,只存在于傳說中,對于開發(fā)者,runtime是做好iOS開發(fā),或是深刻掌握Objective C所必需理解的東西。大公司面試都喜歡問:你對runtime熟悉嗎?并不是runtime在開發(fā)中經(jīng)常用到,我認為它是OC最核心的部分,只有掌握好它,你才能理解其底層的原理,而不是做一個只會造輪子的碼農(nóng)。要練成蓋世神功,需先奠定自身深厚的內(nèi)功,而tuntime就是iOS開發(fā)中的內(nèi)功。
那么runtime到底是什么鬼?
runtime是一個c和匯編寫的動態(tài)庫(感謝Lision的指正),它就像一個小小的系統(tǒng),將OC和C緊密關(guān)聯(lián),這個系統(tǒng)主要做兩件事 :
1、封裝C語言的結(jié)構(gòu)體和函數(shù),讓開發(fā)者在運行時創(chuàng)建、檢查或者修改類、對象和方法等等。
2、傳遞消息,找出方法的最終執(zhí)行代碼。
聽起來蠻抽象的,我們來點通俗的吧?沒問題~~
我們先寫一句OC的代碼
[zhangsan walkTheDog];
那么在運行時runtime會將它轉(zhuǎn)化成C語言的代碼
objc_msgSend(zhangsan, @selector(walkTheDog));
這個方法就是發(fā)送消息的方法,類似這樣的方法runtime提供了很多,比如:
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount ); // 獲取屬性列表
Method * class_copyMethodList ( Class cls, unsigned int *outCount ); // 獲取所有方法的數(shù)組
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types ); // 添加方法
那么我們可以利用這些方法干點什么?
1、遍歷對象的屬性
比如,看看zhangsan的有哪些屬性(身高:180、年齡:18)
2、動態(tài)添加/修改屬性,動態(tài)添加/修改/替換方法
比如,修改zhangsan的身高為190、年齡為20,替換walkTheDog方法(變成walkTheBigDog),給他添加一個新方法(walkTheCat)等等
3、動態(tài)創(chuàng)建類/對象/協(xié)議等等
比如,創(chuàng)建一個新的對象:lisi
4、方法攔截調(diào)用
比如,給zhangsan發(fā)送一個walkTheDog消息,但是zhangsan不知道怎么walk?。]實現(xiàn)該方法),那我們可以攔截下,給該方法動態(tài)添加一個實現(xiàn),甚至可以講該方法定向或者打包給lisi(其他對象),讓lisi來walk。
以上就是runtime的通俗解釋,只是稍微舉個例子,更多用法大家可以發(fā)揮聰明才智,舉一反三。
方法調(diào)用流程
通俗地講,調(diào)用方法(包含實例方法和類方法)相當于給一個對象發(fā)送消息。
所以,實際上,類本身也是一個對象(關(guān)于Class這一塊就不再這里展開了)。
當我們調(diào)用一個方法時,是這樣的:
Instance:調(diào)用實例方法時,會到對象所屬的類的方法列表中查找。
Class:調(diào)用類方法時,會到類的metaClass的方法列表中查找。
下面以實例對象調(diào)用方法[blackDog walk]為例描述方法調(diào)用的流程:
1、編譯器會把`[blackDog walk]`轉(zhuǎn)化為`objc_msgSend(blackDog,SEL)`,SEL為@selector(walk)。
2、Runtime會在blackDog對象所對應(yīng)的Dog類的方法緩存列表里查找方法的SEL
3、如果沒有找到,則在Dog類的方法分發(fā)表查找方法的SEL。(類由對象isa指針指向,方法分發(fā)表即methodList)
4、如果沒有找到,則在其父類(設(shè)Dog類的父類為Animal類)的方法分發(fā)表里查找方法的SEL(父類由類的superClass指向)
5、如果沒有找到,則沿繼承體系繼續(xù)下去,最終到達NSObject類。
6、如果在234的其中一步中找到,則定位了方法實現(xiàn)的入口,執(zhí)行具體實現(xiàn)
7、如果最后還是沒有找到,會面臨兩種情況:
``(1) 如果是使用`[blackDog walk]`的方式調(diào)用方法``
``(2) 使用`[blackDog performSelector:@selector(walk)]`的方式調(diào)用方法``
第一種情況編譯器會報錯,第二種需要到運行時才能確定對象能否接收指定的消息,這時候會進入消息轉(zhuǎn)發(fā)的流程:
消息轉(zhuǎn)發(fā)流程
1、動態(tài)方法解析
接收到未知消息時(假設(shè)blackDog的walk方法尚未實現(xiàn)),runtime會調(diào)用+resolveInstanceMethod:(實例方法)或者+resolveClassMethod:(類方法)
在該方法中,我們可以給未知消息新增一個已經(jīng)實現(xiàn)了的方法。
void walkFunc(id self, SEL _cmd) {
//let the dog walk
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString * selString = NSStringFromSelector(sel);
if ([selString isEqualToString:@"walk"]) {
class_addMethod(self.class, @selector(walk), (IMP)walkFunc, "@:");
}
return [super resolveInstanceMethod:sel];
}
2、備用接收者
如果以上方法沒有做處理,runtime會調(diào)用- (id)forwardingTargetForSelector:(SEL)aSelector方法。
如果該方法返回了一個非nil(也不能是self)的對象,而且該對象實現(xiàn)了這個方法,那么這個對象就成了消息的接收者,消息就被分發(fā)到該對象。
適用情況:通常在對象內(nèi)部使用,讓內(nèi)部的另外一個對象處理消息,在外面看起來就像是該對象處理了消息。
比如:blackDog讓女朋友whiteDog來接收這個消息
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSString * selString = NSStringFromSelector(aSelector);
if ([selString isEqualToString:@"walk"]) {
return self.whiteDog;
}
return [super forwardingTargetForSelector:aSelector];
}
3、完整消息轉(zhuǎn)發(fā)
在- (void)forwardInvocation:(NSInvocation *)anInvocation方法中選擇轉(zhuǎn)發(fā)消息的對象,其中anInvocation對象封裝了未知消息的所有細節(jié),并保留調(diào)用結(jié)果發(fā)送到原始調(diào)用者。
比如:blackDog將消息完整轉(zhuǎn)發(fā)給主人dogOwner來處理
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([DogOwner instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:self.dogOwner];
}
}
4、如果在以上三個方法都沒有處理未知消息,則會引發(fā)異常。
后記
本文主要通俗地描述了runtime的概念,并對其主要作用做了簡單的概括,旨在給讀者拋磚引玉,runtime的奧妙之處就由讀者多多探索學(xué)習(xí)了。
初學(xué)者需要更深入地學(xué)習(xí):
1、基本概念:Class、Ivar、Method等等
2、消息轉(zhuǎn)發(fā)機制
3、在<objc/runtime.h>中理解runtime提供的方法和功能
4、在實際開發(fā)中如何靈活運用runtime
^_^相對于理論,程序員總是更擅長用代碼來表達,博主接下來將陸續(xù)更新runtime實戰(zhàn)應(yīng)用系列文章,敬請關(guān)注。
傳送門:
iOS runtime實戰(zhàn)應(yīng)用:成員變量和屬性
iOS runtime實戰(zhàn)應(yīng)用:關(guān)聯(lián)對象
iOS runtime實戰(zhàn)應(yīng)用:Method Swizzling