iOS 關(guān)于Runtime的騷操作

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如下:

  1. 獲取類實(shí)例的成員變量列表
+ (NSArray *)fetchIvarList:(Class)class;
  1. 獲取類實(shí)例的屬性列表
+ (NSArray *)fetchPropertyList:(Class)class;
  1. 獲取類實(shí)例的方法列表
+ (NSArray *)fetchMethodList:(Class)class;
  1. 為類實(shí)例動態(tài)添加實(shí)例方法,或者改變某methodSel的methodSelImpl
+ (void)addMethod:(Class)class method:(SEL)methodSel method:(SEL)methodSelImpl;
  1. 交換類實(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ù)。

例如:

  1. 響應(yīng)實(shí)例對象的私有方法,提供可變參數(shù)的注入,用于不確定參數(shù)個數(shù)的私有方法
CPFTestClass *test = CPFTestClass.new;
[test invoke:@"testInvokSelectorWithArguments:arg2:" args:@"參數(shù)1",@"參數(shù)2",nil];
  1. 根據(jù) Class Name 響應(yīng)私有方法
// CPFTestClass 是自定義類的類名
[@"CPFTestClass" invokeClassMethod:@"testClassMethod"];

以上僅展示一些相關(guān)API,具體說明和使用請參考Demo,內(nèi)附有完整注釋。

特別的

除此之外,里面一些實(shí)現(xiàn)的細(xì)節(jié)需要簡單的說明一下,最主要的是NSInvocation相關(guān)的理解:

  1. NSInvocation對象被用于對象存儲以及對象與Application之間的消息轉(zhuǎn)發(fā);
  2. 自定義NSInvocation對象,需要提供相應(yīng)類型的NSMethodSignature對象、arguments、target、返回值類型等,對NSInvocation對象執(zhí)行 - invoke 方法,來執(zhí)行響應(yīng)的signature,并得到Return Type;
  3. 在2中提到的響應(yīng)的arguments需要特別注意參數(shù)類型的問題,一旦類型出現(xiàn)錯誤可能引發(fā)意想不到的Crash。但萬幸的是NSMethodSignature對象提供 -getArgumentTypeAtIndex: 的實(shí)力方法,可以返回當(dāng)前索引位置的參數(shù)類型,不過參數(shù)類型是 char * 型的;
  4. @encode 關(guān)鍵字,可以將類型轉(zhuǎn)換為 char * 型的字符串,如@encode(int) ,結(jié)合strcmp這個C標(biāo)準(zhǔn)函數(shù)可以判斷參數(shù)類型是否相同的問題;
  5. 執(zhí)行的返回結(jié)果,通過NSInvocation對象的 - getReturnValue: 實(shí)力方法得到,其中的參數(shù)是一個地址指針,用來指向返回值變量;
  6. 除此之外,提供k_COVERT_ARRAY_FROM_args宏定義,用于將OC方法的可變參數(shù)轉(zhuǎn)換成NSArray;
  7. 需要知道的是,編譯器在處理可變參數(shù)的時候,是根據(jù)第一個可變參數(shù)在內(nèi)存中的地址、參數(shù)類型、偏移量等動態(tài)的計算出下一個參數(shù)的位置,從而取得相應(yīng)的值,直到讀取到nil為止;
  8. 在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哦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容