一、如何在項目中使用runtime
第一步: 在需要使用的類中導入文件
#import
第二步: 在Build Setting中搜索msg,

二、本篇主要講述了以下幾種runtime用法,有一定基礎用于提醒自己的玩家可以直接看表格,其他玩家請繼續(xù)往下看:
用法關(guān)鍵函數(shù)
動態(tài)獲取類名const char *class_getName(Class cls)
動態(tài)獲取類的成員變量Ivar *class_copyIvarList(Class cls, unsigned int *outCount)、
const char *ivar_getName(Ivar v)、
const char *ivar_getTypeEncoding(Ivar v)
動態(tài)獲取類的屬性列表objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)、
const char *property_getName(objc_property_t property)
動態(tài)獲取類的實例方法列表Method *class_copyMethodList(Class cls, unsigned int *outCount)、
SEL method_getName(Method m)
動態(tài)獲取類所遵循的協(xié)議列表Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount)、
const char *protocol_getName(Protocol *p)
動態(tài)添加新的方法Method class_getInstanceMethod(Class cls, SEL name)、
IMP method_getImplementation(Method m)、
const char *method_getTypeEncoding(Method m)、
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
類的實例方法實現(xiàn)交換Method class_getInstanceMethod(Class cls, SEL name)、
void method_exchangeImplementations(Method m1, Method m2)
動態(tài)屬性關(guān)聯(lián)void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)、
id objc_getAssociatedObject(id object, const void *key)
消息發(fā)送與消息轉(zhuǎn)發(fā)機制+ (BOOL)resolveInstanceMethod:(SEL)sel、
- (id)forwardingTargetForSelector:(SEL)aSelector、
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector、
- (void)forwardInvocation:(NSInvocation *)anInvocation
我們首先封裝一個測試類TestClass,其中需要包含遵守協(xié)議,并添加公有屬性、私有屬性、私有成員變量、公有實例方法、私有實例方法、類方法等。
這些內(nèi)容的添加主要便于之后的測試。

TestClass方法變量聲明.png

TestClass私有變量.png

TestClass方法實現(xiàn).png
一、動態(tài)獲悉類結(jié)構(gòu)
1. 動態(tài)獲取類名
采用class_getName(cls)在運行時獲取類的名稱。將char類型的指針轉(zhuǎn)換成NSString類型進行返回。

獲取類名.png
2. 動態(tài)獲取成員變量
采用class_copyIvarList(cls, count)獲取成員變量列表。使用ivar_getName(variable)來輸出成員變量名稱,ivar_getTypeEncoding(variable)來輸出成員變量類型。
我們通過將所得數(shù)據(jù)組合成NSDictionary來存儲單個變量,若干個字典組成NSArray作為屬性列表的返回。

獲取成員變量.png
使用TestClass進行用例測試。由于是調(diào)用上述方法獲取TestClass的成員變量,到了運行時階段實際就不存在公有私有之分。OC中的類在ARC情況下添加的屬性,其實就是自動生成其get方法與set方法。
所有獲取的成員列表中肯定帶有成員屬性,成員屬性的名稱前方帶有下劃線用于成員變量進行區(qū)分。
下方中各基本類型由特殊字母代替,可以看出i代表int類型,c代表bool類型,d表示double類型,f表示float類型。而如果是引用類型則直接是一個字符串顯示,比如NSString類型就是@"NSString"。

測試成員列表打印.png
3. 動態(tài)獲取成員屬性列表
上方獲取了類的成員變量,那么下方進行屬性列表的獲取。屬性區(qū)分于變量主要是它們擁有完整的set方法和get方法。
我們使用class_copyPropertyList(cls, count)來獲取屬性列表,通過property_getName(property)來獲取屬性名稱。

獲取屬性列表.png
下方dynamic的屬性是我們使用runtime進行動態(tài)添加的。

測試屬性列表打印.png
4. 獲取類的實例方法
我們通過class_copyMethodList(cls, count)來獲取實例方法列表,通過method_getName(method)來獲取實例方法名稱。

獲取實例方法.png
下方打印了所有TestClass類的實例方法,當然包括成員屬性的set方法和get方法。其中.cxx_destruct方法不確認歸屬于何處,也許dealloc方法的自我實現(xiàn)?

測試實例方法列表打印.png
5. 獲取類的協(xié)議列表
我們使用class_copyProtocolList(cls, count)來獲取協(xié)議列表,使用protocol_getName(protocol)來獲取協(xié)議名稱

獲取協(xié)議列表.png
二、動態(tài)操作類方法
1. 動態(tài)添加方法實現(xiàn)
其添加原理旨在使用class_getInstanceMethod(cls, methodName)獲取相關(guān)的方法聲明以及使用method_getImplementation(method)獲取相關(guān)的方法實現(xiàn)。將它們進行組合后,使用class_addMethod(cls, methodName, method, type)進行方法的添加。

動態(tài)添加方法實現(xiàn).png
2. 實現(xiàn)方法交換
通過class_getInstanceMethod(cls, methodName)獲取到需要交換的兩個方法,直接使用method_exchangeImplementation(methodA, methodB)進行方法替換即可。

實現(xiàn)方法交換.png
通過類目為測試類封裝一個針對交換方法的測試用例。
如果是普通情況下,沒有交換。在replaceMethod中調(diào)用本身勢必會造成死循環(huán)。
如是如果交換方法成功,那么此時在replaceMethod中調(diào)用replaceMethod,其實此時調(diào)用的是exchangeMethodA。由于exchangeMethodA不存在死循環(huán),故在測試時,調(diào)用了封裝的交換方法后,進一步又調(diào)用了replaceMethod,其實只是調(diào)用了exchangeMethodA而已。

交換方法的封裝.png
三、屬性關(guān)聯(lián)
屬性關(guān)聯(lián)可以說是runtime最普通的打開方式了。通過為屬性聲明一個靜態(tài)名稱,調(diào)用void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)實現(xiàn)新增屬性的set方法,調(diào)用id objc_getAssociatedObject(id object, const void *key)實現(xiàn)新增屬性的get方法即可。

動態(tài)添加屬性.png
四、消息處理與消息轉(zhuǎn)發(fā)
消息處理過程:
當你調(diào)用一個類的方法時,先在本類中的方法緩存列表中進行查詢,如果在緩存列表中找到了該方法的實現(xiàn),就執(zhí)行;如果找不到就在本類中的方法列表中進行查找。
在本類方法列表中查找到相應的方法實現(xiàn)后就進行調(diào)用,如果沒找到,就去父類中進行查找;如果在父類中的方法列表中找到了相應方法的實現(xiàn)。
當在方法緩存列表,本類中的方法列表以及父類中的方法列表中都找不到相應的實現(xiàn),到程序崩潰以前還會經(jīng)歷以下過程:
消息處理
如果一直尋找方法直到父類中都找不到方法實現(xiàn)時會執(zhí)行+ (BOOL)resolveInstanceMethod:(SEL)sel類方法。
如果返回NO,則表明不做任何處理,繼續(xù)下一步。如果返回YES,就說明該方法中對找不到實現(xiàn)的方法進行了處理。
我們就可以在此方法中為找不到實現(xiàn)的SEL動態(tài)添加一個方法實現(xiàn),添加完畢后,就會執(zhí)行我們添加的方法實現(xiàn)。
下一次程序再找不到該類某個方法的實現(xiàn)時,就不會因為找不到而崩潰了。

消息處理.png
2.消息轉(zhuǎn)發(fā)
如果不對上述消息進行處理的話,也就是+ (BOOL)resolveInstanceMethod:(SEL)sel方法返回NO時。便進入了下一步消息轉(zhuǎn)發(fā)。
即執(zhí)行- (id)forwardingTargetForSelector:(SEL)aSelector方法。該方法會返回一個類對象,該類的對象有SEL對應的實現(xiàn),當調(diào)用這個找不到方法時,就會轉(zhuǎn)發(fā)到ExtClass中進行處理。
此時完成消息轉(zhuǎn)發(fā)。如果該方法返回self或者nil,說明不對相應的方法進行轉(zhuǎn)發(fā),那就再走下一步。

消息轉(zhuǎn)發(fā).png
3.消息常規(guī)轉(zhuǎn)發(fā)
如果不將消息轉(zhuǎn)發(fā)給其他類的對象,則此時代表自己進行處理。即上述的方法中返回self或者nil。
此時執(zhí)行- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector來獲取方法的參數(shù)以及返回數(shù)據(jù)類型,即可以理解為該方法的簽名。
如果此時再次返回nil,那么消息轉(zhuǎn)發(fā)結(jié)束。程序崩潰,報出找不到相應的方法實現(xiàn)的崩潰消息。
下方方法執(zhí)行的先決條件,是要在+ (BOOL)resolveInstanceMethod:(SEL)sel中返回NO。然后下方也是進行將方法轉(zhuǎn)給ExtClass的實現(xiàn)。

消息常規(guī)轉(zhuǎn)發(fā).png
本文項目Github鏈接地址:https://github.com/LibertyLeo/Runtime-Usage