CPFRuntimeKit
源代碼傳送門,編寫不易,看完給個Star哦。
用來做什么?
一個用來處理 Objective-C Runtime 騷操作的小工具,包含一下幾個功能:
- CPFRuntimeHelper —— 為你指定的Class提供便捷的Hacker方法;
- CPFRuntimeInvoker —— 為你指定的Class提供便捷的響應(yīng)方法;
- CPFWeakSingleton —— 一種另類的單例模式實(shí)現(xiàn)方式,解決單例在生命周期不釋放的問題。
- CPFTestClass —— 結(jié)合CPFRuntimeHelper,處理Runtime消息轉(zhuǎn)發(fā)。
一、CPFRuntimeHelper
提供簡單易用的API用于快速獲取成員變量列表、屬性列表、類方法列表、實(shí)例方法列表、協(xié)議列表,此外還支持動態(tài)注入實(shí)例方法、交換實(shí)例方法實(shí)現(xiàn)等。
API如下:
- 獲取類實(shí)例的成員變量列表
+ (NSArray *)fetchIvarList:(Class)class;
- 獲取類實(shí)例的屬性列表
+ (NSArray *)fetchPropertyList:(Class)class;
- 獲取類實(shí)例的方法列表
+ (NSArray *)fetchMethodList:(Class)class;
- 為類實(shí)例動態(tài)添加實(shí)例方法,或者改變某methodSel的methodSelImpl
+ (void)addMethod:(Class)class method:(SEL)methodSel method:(SEL)methodSelImpl;
- 交換類實(shí)例的方法實(shí)現(xiàn)
+ (void)methodSwap:(Class)class firstMethod:(SEL)method1 secondMethod:(SEL)method2;
以上僅展示一些相關(guān)API,具體說明和使用請參考Demo,內(nèi)附有完整注釋。
二、CPFRuntimeInvoker
與CPFRuntimeHelper不同,CPFRuntimeInvoker的實(shí)現(xiàn)和調(diào)用方式主要有兩種,一種是為NSObject類添加Category,這樣一來,就能為所有的類,包括自定義的類添加實(shí)例方法和類方法了。
CPFRuntimeInvoker就是利用了Category的特性為NSObject和NSString添加擴(kuò)展,這樣就能通過Object和String,直接執(zhí)行私有方法,并注入可變參數(shù)。
例如:
- 響應(yīng)實(shí)例對象的私有方法,提供可變參數(shù)的注入,用于不確定參數(shù)個數(shù)的私有方法
CPFTestClass *test = CPFTestClass.new;
[test invoke:@"testInvokSelectorWithArguments:arg2:" args:@"參數(shù)1",@"參數(shù)2",nil];
- 根據(jù) Class Name 響應(yīng)私有方法
// CPFTestClass 是自定義類的類名
[@"CPFTestClass" invokeClassMethod:@"testClassMethod"];
以上僅展示一些相關(guān)API,具體說明和使用請參考Demo,內(nèi)附有完整注釋。
特別的
除此之外,里面一些實(shí)現(xiàn)的細(xì)節(jié)需要簡單的說明一下,最主要的是NSInvocation相關(guān)的理解:
- NSInvocation對象被用于對象存儲以及對象與Application之間的消息轉(zhuǎn)發(fā);
- 自定義NSInvocation對象,需要提供相應(yīng)類型的NSMethodSignature對象、arguments、target、返回值類型等,對NSInvocation對象執(zhí)行 - invoke 方法,來執(zhí)行響應(yīng)的signature,并得到Return Type;
- 在2中提到的響應(yīng)的arguments需要特別注意參數(shù)類型的問題,一旦類型出現(xiàn)錯誤可能引發(fā)意想不到的Crash。但萬幸的是NSMethodSignature對象提供 -getArgumentTypeAtIndex: 的實(shí)力方法,可以返回當(dāng)前索引位置的參數(shù)類型,不過參數(shù)類型是 char * 型的;
- @encode 關(guān)鍵字,可以將類型轉(zhuǎn)換為 char * 型的字符串,如@encode(int) ,結(jié)合strcmp這個C標(biāo)準(zhǔn)函數(shù)可以判斷參數(shù)類型是否相同的問題;
- 執(zhí)行的返回結(jié)果,通過NSInvocation對象的 - getReturnValue: 實(shí)力方法得到,其中的參數(shù)是一個地址指針,用來指向返回值變量;
- 除此之外,提供k_COVERT_ARRAY_FROM_args宏定義,用于將OC方法的可變參數(shù)轉(zhuǎn)換成NSArray;
- 需要知道的是,編譯器在處理可變參數(shù)的時候,是根據(jù)第一個可變參數(shù)在內(nèi)存中的地址、參數(shù)類型、偏移量等動態(tài)的計算出下一個參數(shù)的位置,從而取得相應(yīng)的值,直到讀取到nil為止;
- 在6中的宏定義,就是利用了這個特性將可變參數(shù)轉(zhuǎn)換成NSArray的。
三、CPFWeakSingleton
在開發(fā)中,我們通常會使用單例模式,但是單例會有一個不好的問題就是,在整個程序的運(yùn)行周期中,單例對象都不會被釋放,從而會對內(nèi)存造成一定的影響,那么我們可以利用 weak 關(guān)鍵字對單例模式進(jìn)行改造,達(dá)到如果單例對象被外部持有,則永遠(yuǎn)不會被釋放,一旦不被外部持有,則會在 Runloop 時被回收內(nèi)存的目的。
以名為CPFWeakSingleton的類名為例,代碼如下:
@implementation CPFWeakSingleton
+ (instancetype)sharedInstacne {
return [[self alloc] init];
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static __weak CPFWeakSingleton *weakInstance;
CPFWeakSingleton *strongInstance = weakInstance;
@synchronized(self) {
if (strongInstance == nil) {
strongInstance = [super allocWithZone:zone];
weakInstance = strongInstance;
}
}
return strongInstance;
}
@end
下面對其進(jìn)行驗證:
_strongInstance = [CPFWeakSingleton sharedInstacne];
NSLog(@"1---%p",_strongInstance);
_strongInstance.testStr = @"保留所有權(quán)";
NSLog(@"2---%p",_strongInstance);
sleep(5);
NSLog(@"3---%p",[CPFWeakSingleton sharedInstacne]);
運(yùn)行結(jié)果如下:
1---0x604000202ea0
2---0x604000202ea0
3---0x604000202ea0
可以看出,當(dāng)我們通過_strongInstance變量持有單例對象時,在經(jīng)過 Runloop 之后,單例對象也不會被釋放(sleep函數(shù)是為了驗證 Runloop 后對象是否會被回收)。
然而我們對上例稍加改動,使_strongInstance被釋放后會發(fā)生什么呢?
_strongInstance = [CPFWeakSingleton sharedInstacne];
NSLog(@"1---%p",_strongInstance);
_strongInstance.testStr = @"保留所有權(quán)";
NSLog(@"2---%p",_strongInstance);
_strongInstance = nil;
sleep(5);
NSLog(@"3---%p",[CPFWeakSingleton sharedInstacne]);
此時的運(yùn)行結(jié)果如下:
1---0x600000009430
2---0x600000009430
3---0x604000007570
可以看出,當(dāng)外部的_strongInstance對象被釋放,不再持有單例對象的時候,或者超出此時單例對象的作用域時(上述代碼未演示),該單例對象也會在 Runloop 中被系統(tǒng)回收,當(dāng)我們再次使用sharedInstacne類方法獲取單例對象的時候,則會創(chuàng)建一個新的單例對象。這樣,就能即使用單例,又解決了產(chǎn)生的單例對象一直占用內(nèi)存資源,而且在整個程序的運(yùn)行周期內(nèi)都不會被釋放的問題。
四、CPFTestClass
有關(guān)消息轉(zhuǎn)發(fā)的一些內(nèi)容,網(wǎng)上有很多相關(guān)的資料說的都很清楚,在這里就不做過多的贅述。本Demo中主要是結(jié)合CPFRuntimeHelper和CPFRuntimeInvoker去強(qiáng)行實(shí)現(xiàn)消息轉(zhuǎn)發(fā)的過程,以及解決消息轉(zhuǎn)發(fā)帶來的doesNotRecognizeSelector異常Crash的問題。
以上僅展示一些相關(guān)API,具體說明和使用請參考Demo,內(nèi)附有完整注釋。
源代碼傳送門,編寫不易,看完給個Star哦。