不論是在面試過程中,還是自己開發項目遇到問題時,runtime經常能解決一些常規情況下遇到的難以解決的部分。
今天閱讀過一篇關于runtime常規用法的文章,覺得不錯便想要通過簡化原有文章進行知識存儲。
文章參考出處:http://www.cnblogs.com/ludashi/p/6294112.html
文章參考作者:青玉伏案
本篇主要講述了以下幾種runtime用法,有一定基礎用于提醒自己的玩家可以直接看表格,其他玩家請繼續往下看:
用法 | 關鍵函數 |
---|---|
動態獲取類名 | const char *class_getName(Class cls) |
動態獲取類的成員變量 |
Ivar *class_copyIvarList(Class cls, unsigned int *outCount) 、<br />const char *ivar_getName(Ivar v) 、<br />const char *ivar_getTypeEncoding(Ivar v)
|
動態獲取類的屬性列表 |
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) 、<br />const char *property_getName(objc_property_t property)
|
動態獲取類的實例方法列表 |
Method *class_copyMethodList(Class cls, unsigned int *outCount) 、<br />SEL method_getName(Method m)
|
動態獲取類所遵循的協議列表 |
Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount) 、<br />const char *protocol_getName(Protocol *p)
|
動態添加新的方法 |
Method class_getInstanceMethod(Class cls, SEL name) 、<br />IMP method_getImplementation(Method m) 、<br />const char *method_getTypeEncoding(Method m) 、<br />BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
|
類的實例方法實現交換 |
Method class_getInstanceMethod(Class cls, SEL name) 、<br />void method_exchangeImplementations(Method m1, Method m2)
|
動態屬性關聯 |
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) 、<br />id objc_getAssociatedObject(id object, const void *key)
|
消息發送與消息轉發機制 |
+ (BOOL)resolveInstanceMethod:(SEL)sel 、<br />- (id)forwardingTargetForSelector:(SEL)aSelector 、<br />- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 、<br />- (void)forwardInvocation:(NSInvocation *)anInvocation
|
我們首先封裝一個測試類TestClass,其中需要包含遵守協議,并添加公有屬性、私有屬性、私有成員變量、公有實例方法、私有實例方法、類方法等。
這些內容的添加主要便于之后的測試。
TestClass方法變量聲明.png
TestClass私有變量.png
TestClass方法實現.png
一、動態獲悉類結構
1. 動態獲取類名
采用`class_getName(cls)`在運行時獲取類的名稱。將char類型的指針轉換成NSString類型進行返回。
獲取類名.png
2. 動態獲取成員變量
采用`class_copyIvarList(cls, count)`獲取成員變量列表。使用`ivar_getName(variable)`來輸出成員變量名稱,`ivar_getTypeEncoding(variable)`來輸出成員變量類型。
我們通過將所得數據組合成NSDictionary來存儲單個變量,若干個字典組成NSArray作為屬性列表的返回。
獲取成員變量.png
使用TestClass進行用例測試。由于是調用上述方法獲取TestClass的成員變量,到了運行時階段實際就不存在公有私有之分。OC中的類在ARC情況下添加的屬性,其實就是自動生成其get方法與set方法。
所有獲取的成員列表中肯定帶有成員屬性,成員屬性的名稱前方帶有下劃線用于成員變量進行區分。
下方中各基本類型由特殊字母代替,可以看出i代表int類型,c代表bool類型,d表示double類型,f表示float類型。而如果是引用類型則直接是一個字符串顯示,比如NSString類型就是@"NSString"。
測試成員列表打印.png
3. 動態獲取成員屬性列表
上方獲取了類的成員變量,那么下方進行屬性列表的獲取。屬性區分于變量主要是它們擁有完整的set方法和get方法。
我們使用`class_copyPropertyList(cls, count)`來獲取屬性列表,通過`property_getName(property)`來獲取屬性名稱。
獲取屬性列表.png
下方dynamic的屬性是我們使用runtime進行動態添加的。
測試屬性列表打印.png
4. 獲取類的實例方法
我們通過`class_copyMethodList(cls, count)`來獲取實例方法列表,通過`method_getName(method)`來獲取實例方法名稱。
獲取實例方法.png
下方打印了所有TestClass類的實例方法,當然包括成員屬性的set方法和get方法。**其中`.cxx_destruct`方法不確認歸屬于何處,也許dealloc方法的自我實現?**
測試實例方法列表打印.png
5. 獲取類的協議列表
我們使用`class_copyProtocolList(cls, count)`來獲取協議列表,使用`protocol_getName(protocol)`來獲取協議名稱
獲取協議列表.png
二、動態操作類方法
1. 動態添加方法實現
其添加原理旨在使用`class_getInstanceMethod(cls, methodName)`獲取相關的方法聲明以及使用`method_getImplementation(method)`獲取相關的方法實現。將它們進行組合后,使用`class_addMethod(cls, methodName, method, type)`進行方法的添加。
動態添加方法實現.png
2. 實現方法交換
通過`class_getInstanceMethod(cls, methodName)`獲取到需要交換的兩個方法,直接使用`method_exchangeImplementation(methodA, methodB)`進行方法替換即可。
實現方法交換.png
通過類目為測試類封裝一個針對交換方法的測試用例。
- 如果是普通情況下,沒有交換。在replaceMethod中調用本身勢必會造成死循環。
- 如是如果交換方法成功,那么此時在replaceMethod中調用replaceMethod,其實此時調用的是exchangeMethodA。由于exchangeMethodA不存在死循環,故在測試時,調用了封裝的交換方法后,進一步又調用了replaceMethod,其實只是調用了exchangeMethodA而已。
交換方法的封裝.png
三、屬性關聯
屬性關聯可以說是runtime最普通的打開方式了。通過為屬性聲明一個靜態名稱,調用`void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)`實現新增屬性的set方法,調用`id objc_getAssociatedObject(id object, const void *key)`實現新增屬性的get方法即可。
動態添加屬性.png
四、消息處理與消息轉發
- 消息處理過程:
- 當你調用一個類的方法時,先在本類中的方法緩存列表中進行查詢,如果在緩存列表中找到了該方法的實現,就執行;如果找不到就在本類中的方法列表中進行查找。
- 在本類方法列表中查找到相應的方法實現后就進行調用,如果沒找到,就去父類中進行查找;如果在父類中的方法列表中找到了相應方法的實現。
當在方法緩存列表,本類中的方法列表以及父類中的方法列表中都找不到相應的實現,到程序崩潰以前還會經歷以下過程:
- 消息處理
- 如果一直尋找方法直到父類中都找不到方法實現時會執行
+ (BOOL)resolveInstanceMethod:(SEL)sel
類方法。
- 如果返回NO,則表明不做任何處理,繼續下一步。如果返回YES,就說明該方法中對找不到實現的方法進行了處理。
- 我們就可以在此方法中為找不到實現的SEL動態添加一個方法實現,添加完畢后,就會執行我們添加的方法實現。
- 下一次程序再找不到該類某個方法的實現時,就不會因為找不到而崩潰了。
消息處理.png
2.消息轉發
- 如果不對上述消息進行處理的話,也就是
+ (BOOL)resolveInstanceMethod:(SEL)sel
方法返回NO時。便進入了下一步消息轉發。
- 即執行
- (id)forwardingTargetForSelector:(SEL)aSelector
方法。該方法會返回一個類對象,該類的對象有SEL對應的實現,當調用這個找不到方法時,就會轉發到ExtClass中進行處理。 - 此時完成消息轉發。如果該方法返回self或者nil,說明不對相應的方法進行轉發,那就再走下一步。
消息轉發.png
3.消息常規轉發
- 如果不將消息轉發給其他類的對象,則此時代表自己進行處理。即上述的方法中返回self或者nil。
此時執行
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
來獲取方法的參數以及返回數據類型,即可以理解為該方法的簽名。如果此時再次返回nil,那么消息轉發結束。程序崩潰,報出找不到相應的方法實現的崩潰消息。
下方方法執行的先決條件,是要在
+ (BOOL)resolveInstanceMethod:(SEL)sel
中返回NO。然后下方也是進行將方法轉給ExtClass的實現。
消息常規轉發.png
本文項目Github鏈接地址:https://github.com/LibertyLeo/Runtime-Usage