需求: 本身項(xiàng)目已經(jīng)很大,要新增頁面訪問統(tǒng)計(jì)功能。
Method Swizzling原理:Method Swizzling是發(fā)生在運(yùn)行時(shí)的,主要是將兩個(gè)method進(jìn)行交換,我們可以將Method Swizzling代碼寫到任何地方,但是只有這段Method Swizzing代碼執(zhí)行完畢之后互換才起作用。
Method Swizzling使用:在實(shí)現(xiàn)Method Swizzling時(shí),核心代碼主要就是一個(gè)runtime的C語言API:
#import "UIViewController+swizzling.h"
#import <objc/runtime.h>
@implementation UIViewController (swizzling)
+ (void)load {
// 通過class_getInstanceMethod()函數(shù)從當(dāng)前對(duì)象中的method list獲取method結(jié)構(gòu)體,如果是類方法就使用class_getClassMethod()函數(shù)獲取。
Method fromMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));
Method toMethod = class_getInstanceMethod([self class], @selector(swizzlingViewDidLoad));
/**
* 我們?cè)谶@里使用class_addMethod()函數(shù)對(duì)Method Swizzling做了一層驗(yàn)證,如果self沒有實(shí)現(xiàn)被交換的方法,會(huì)導(dǎo)致失敗。
* 而且self沒有交換的方法實(shí)現(xiàn),但是父類有這個(gè)方法,這樣就會(huì)調(diào)用父類的方法,結(jié)果就不是我們想要的結(jié)果了。
* 所以我們?cè)谶@里通過class_addMethod()的驗(yàn)證,如果self實(shí)現(xiàn)了這個(gè)方法,class_addMethod()函數(shù)將會(huì)返回NO,我們就可以對(duì)其進(jìn)行交換了。
*/
if (!class_addMethod([self class], @selector(swizzlingViewDidLoad), method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
method_exchangeImplementations(fromMethod, toMethod);
}
}
// 我們自己實(shí)現(xiàn)的方法,也就是和self的viewDidLoad方法進(jìn)行交換的方法。
- (void)swizzlingViewDidLoad {
NSString *str = [NSString stringWithFormat:@"%@", self.class];
// 我們?cè)谶@里加一個(gè)判斷,將系統(tǒng)的UIViewController的對(duì)象剔除掉
if(![str containsString:@"UI"]){
NSLog(@"統(tǒng)計(jì)打點(diǎn) : %@", self.class);
}
[self swizzlingViewDidLoad];
}
@end
Method Swizzling類簇:項(xiàng)目開發(fā)過程中,經(jīng)常因?yàn)镹SArray數(shù)組越界或者NSDictionary的key或者value值為nil等問題導(dǎo)致的崩潰,對(duì)于這些問題蘋果并不會(huì)報(bào)一個(gè)警告,而是直接崩潰,感覺蘋果這樣確實(shí)有點(diǎn)“太狠了”。
由此,我們可以根據(jù)上面所學(xué),對(duì)NSArray、NSMutableArray、NSDictionary、NSMutableDictionary等類進(jìn)行Method Swizzling,實(shí)現(xiàn)方式還是按照上面的例子來做。但是....你發(fā)現(xiàn)Method Swizzling根本就不起作用,代碼也沒寫錯(cuò)啊,到底是什么鬼?
這是因?yàn)镸ethod Swizzling對(duì)NSArray這些的類簇是不起作用的。因?yàn)檫@些類簇類,其實(shí)是一種抽象工廠的設(shè)計(jì)模式。抽象工廠內(nèi)部有很多其它繼承自當(dāng)前類的子類,抽象工廠類會(huì)根據(jù)不同情況,創(chuàng)建不同的抽象對(duì)象來進(jìn)行使用。例如我們調(diào)用NSArray的objectAtIndex:方法,這個(gè)類會(huì)在方法內(nèi)部判斷,內(nèi)部創(chuàng)建不同抽象類進(jìn)行操作。
所以也就是我們對(duì)NSArray類進(jìn)行操作其實(shí)只是對(duì)父類進(jìn)行了操作,在NSArray內(nèi)部會(huì)創(chuàng)建其他子類來執(zhí)行操作,真正執(zhí)行操作的并不是NSArray自身,所以我們應(yīng)該對(duì)其“真身”進(jìn)行操作。
#import "NSArray+LXZArray.h"
#import "objc/runtime.h"
@implementation NSArray (LXZArray)
+ (void)load {
Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(lxz_objectAtIndex:));
method_exchangeImplementations(fromMethod, toMethod);
}
- (id)lxz_objectAtIndex:(NSUInteger)index {
if (self.count-1 < index) {
// 這里做一下異常處理,不然都不知道出錯(cuò)了。
@try {
return [self lxz_objectAtIndex:index];
}
@catch (NSException *exception) {
// 在崩潰后會(huì)打印崩潰信息,方便我們調(diào)試。
NSLog(@"---------- %s Crash Because Method %s ----------\n", class_getName(self.class), __func__);
NSLog(@"%@", [exception callStackSymbols]);
return nil;
}
@finally {}
} else {
return [self lxz_objectAtIndex:index];
}
}
@end
__NSArrayI才是NSArray真正的類,而NSMutableArray又不一樣。我們可以通過runtime函數(shù)獲取真正的類:
objc_getClass("__NSArrayI")