運行時確實是個好東西扶踊,俗話說學(xué)會了Runtime還不夠裙盾,要懂得如何運用它來為項目帶來便利实胸,那才叫做真正的懂它。其實番官,很多方面我們需要用到它庐完,只是很多時候我們不知道它的存在或者根本不會去了解和深入學(xué)習(xí)它而已。譬如徘熔,不同iOS版本的API兼容問題或是替換原有系統(tǒng)的IMP门躯,又或是通過反射來獲取系統(tǒng)的私有API,還有在APP安全防護和攻擊時也用到它近顷。
說到Runtime生音,不得不說說它的swizzling技術(shù)宁否。這個名詞好像在14年那會挺吸眼球的,在逆向工程中偶爾會見到它的身影缀遍。其實慕匠,說的通俗點就是用自己寫的方法偷換系統(tǒng)的IMP。
最常見的就是通過一個例子說明來談?wù)勥@個技術(shù)域醇。
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class aClass = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
// 通過實例方法來獲取Method
Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);
// 這個是獲取類名台谊,既是獲取metaClass.
// Class aClass = object_getClass((id)self);
// 通過類方法來獲取Method
// Method originalMethod = class_getClassMethod(aClass, originalSelector);
// Method swizzledMethod = class_getClassMethod(aClass, swizzledSelector);
// 為類添加新方法
BOOL didAddMethod =
class_addMethod(aClass,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(aClass,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
備注:class_addMethod是為該類動態(tài)添加方法Method,具體就是通過原有的selector來擴展新的IMP譬挚,
通過它來判斷該類是否已經(jīng)動態(tài)為其添加過該方法锅铅,
如果添加過了,通過class_replaceMethod來替換原有的selector實現(xiàn)减宣,從而達到對換這兩個selector來交換其實現(xiàn)的IMP盐须。否則,通過method_exchangeImplementations來交換兩者的IMP實現(xiàn)漆腌。
這里強調(diào)一下+(void)load贼邓,這個方法是系統(tǒng)第一次裝載程序到內(nèi)存時調(diào)用,而且只調(diào)用一次闷尿,在程序開啟時塑径,程序主要把所有.m文件都加載到內(nèi)存中。還有與之對應(yīng)的方法是+ (void)initialize. 官方介紹如下:
+(void)initialize
The runtime sends initialize to each class in a program exactly one time just before the class,
or any class that inherits from it, is sent its first message from within the program. (Thus the
method may never be invoked if the class is not used.) The runtime sends the initialize
message to classes in a thread-safe manner. Superclasses receive this message before their
subclasses.
+(void)load
The load message is sent to classes and categories that are both dynamically loaded and
statically linked, but only if the newly loaded class or category implements a method that can
respond.
The order of initialization is as follows:
All initializers in any framework you link to.
All +load methods in your image.
All C++ static initializers and C/C++ __attribute__(constructor) functions in your image.
All initializers in frameworks that link to you.
In addition:
A class’s +load method is called after all of its superclasses’ +load methods.
A category +load method is called after the class’s own +load method.
In a custom implementation of load you can therefore safely message other unrelated classes
from the same image, but any load methods implemented by those classes may not have run yet.
Apple的文檔很清楚地說明了initialize和load的區(qū)別在于:load是只要類所在文件被引用就會被調(diào)用填具,而initialize是在類或者其子類的第一個方法被調(diào)用前調(diào)用统舀。所以如果類沒有被引用進項目,就不會有l(wèi)oad調(diào)用劳景;但即使類文件被引用進來誉简,但是沒有使用,那么initialize也不會被調(diào)用枢泰。
它們的相同點在于:方法只會被調(diào)用一次描融。(其實這是相對runtime來說的,后邊會做進一步解釋)衡蚂。
文檔也明確闡述了方法調(diào)用的順序:父類(Superclass)的方法優(yōu)先于子類(Subclass)的方法,類中的方法優(yōu)先于類別(Category)中的方法骏庸。
似乎有點扯遠了毛甲。。具被。玻募。。一姿。
這個技術(shù)真的是很常見七咧,同時可以為我們省去很多麻煩和瑣碎的細(xì)節(jié)跃惫。譬如,如果一個控件的API在iOS7上沒有這個方法艾栋,而iOS8及以上有這個方法爆存,可以通過寫這個控件的分類來判斷不同版本下同時調(diào)用這個方法,但是在該分類中通過版本判斷蝗砾,在iOS7及以下用自己實現(xiàn)類似系統(tǒng)API的方法來替換系統(tǒng)的該方法先较,從而達到不用修改原有的代碼。
還有就是在對NSArray悼粮,NSMutableArray闲勺,NSDictionary,NSMutableDictionary中扣猫,通過字面量訪問方式或者通過objectAtIndex等方法進行訪問時菜循,如果服務(wù)器那邊不小心傳入nil來插入數(shù)組或者字典或訪問越界數(shù)據(jù),都會導(dǎo)致應(yīng)用崩潰申尤。所以癌幕,通過實現(xiàn)NSArray等數(shù)據(jù)結(jié)構(gòu)的分類,來對nil瀑凝,越界等進行判斷處理序芦,防止程序崩潰。但是使用這個技術(shù)粤咪,如果數(shù)據(jù)出現(xiàn)錯誤的情況谚中,很難通過該方法來查找bug,所以要謹(jǐn)慎使用之寥枝。
由此宪塔,我們可以根據(jù)上面所學(xué),對NSArray囊拜、NSMutableArray某筐、NSDictionary、NSMutableDictionary等類進行Method Swizzling冠跷,但是南誊,你發(fā)現(xiàn)Method Swizzling根本就不起作用,代碼也沒寫錯啊蜜托,到底是為什么抄囚?這是因為Method Swizzling對NSArray這些的類簇是不起作用的。因為這些類簇類橄务,其實是一種抽象工廠的設(shè)計模式幔托。抽象工廠內(nèi)部有很多其它繼承自當(dāng)前類的子類,抽象工廠類會根據(jù)不同情況,創(chuàng)建不同的抽象對象來進行使用重挑。例如我們調(diào)用NSArray的objectAtIndex:方法嗓化,這個類會在方法內(nèi)部判斷,內(nèi)部創(chuàng)建不同抽象類進行操作谬哀。
所以也就是我們對NSArray類進行操作其實只是對父類進行了操作刺覆,在NSArray內(nèi)部會創(chuàng)建其他子類來執(zhí)行操作,真正執(zhí)行操作的并不是NSArray自身玻粪,所以我們應(yīng)該對其“真身”進行操作隅津。NSArray的真身是__NSArrayI,而NSMutableArray的真身是__NSArrayM, NSDictionary的真身是__NSDictionaryI,NSMutableDictionary的真身是__NSDictionaryM。這幾個東東是不是很熟悉啊劲室,沒錯伦仍,還記得當(dāng)崩潰時控制臺輸出的信息中就有這幾個關(guān)鍵字的出現(xiàn)嗎?好吧很洋,這種事情你們自己去發(fā)掘好了充蓝。
代碼如下:
#import "NSArray+Extension.h"
#import <objc/runtime.h>
@implementation NSArray (Extension)
+ (void)load {
Method originMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
Method swizzleMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(wt_objectAtIndex:));
method_exchangeImplementations(originMethod, swizzleMethod);
}
// 分類記得要加前綴,否則會和別人寫的分類沖突喉磁,導(dǎo)致只有一個方法映射到IMP中谓苟。
- (id)wt_objectAtIndex:(NSUInteger)index {
if (self.count-1 < index) {
@try {
return [self lxz_objectAtIndex:index];
}
@catch (NSException *exception) {
// 打印崩潰信息,方便調(diào)試
NSLog(@"---------- %s Crash Method %s ----------\n", class_getName(self.class), __func__);
NSLog(@"%@", [exception callStackSymbols]);
return nil;
}
@finally {}
}
else {
return [self wt_objectAtIndex:index];
}
}
@end
總之,合適的地方恰當(dāng)?shù)倪\用該技術(shù),會達到事半功倍的效果飘庄,而且這也給自己的技術(shù)功底提升了不少呢澈侠。