首先參考一下自己之前寫的《method swizzing》這篇,特別是對類簇的methodSwizzing。
Container 類型的crash 指的是容器類的crash,常見的有NSArray/NSMutableArray/NSDictionary/NSMutableDictionary/NSCache的crash。 一些常見的越界、插入nil等錯誤操作均會導致此類crash發(fā)生。 由于產(chǎn)生的原因比較簡單,就不展開來描述了。
該類crash雖然比較容易排查,但是其在app crash概率總比還是挺高,所以有必要對其進行防護。另外NSString也有類似的crash防護措施,在此篇中一并舉例說明。
一、Container類型和NSString類型常見crash
(1) [aMutableDictionary setObject:nil forKey:]; object can not be nil.
(2) [aString hasSuffix:nil]; nil argument crash.
[aString hasPrefix:nil]; nil argument crash.
(3) aString = [NSMutableString stringWithString:nil];nil argument crash.
(4) aString = [[NSString alloc] initWithString:nil]; nil argument crash.
(5) aURL = [NSURL fileURLWithPath:nil]; nil argument crash.
(6) NSArray 數(shù)組越界 crash。
二、使用method swizzing對常見crash防護舉例
這里對NSString、NSMutableDictionary和NSArray常見的幾個crash舉例如下:
(1)對NSString的hasSuffix:和hasPrefix:進行預(yù)防:
#import "NSString+CrashGurad.h"
#import <objc/runtime.h>
@implementation NSString (CrashGurad)
#pragma mark Class Method
+(void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[self class] swizzedMethod:@selector(hasSuffix:) withMethod:@selector(crashGuard_hasSuffix:)];
[[self class] swizzedMethod:@selector(hasPrefix:) withMethod:@selector(crashGuard_hasPrefix:)];
});
}
+(void)swizzedMethod:(SEL)originalSelector withMethod:(SEL )swizzledSelector {
Method fromMethod = class_getInstanceMethod(objc_getClass("__NSCFConstantString"), originalSelector);
Method toMethod = class_getInstanceMethod(objc_getClass("__NSCFConstantString"), swizzledSelector);
method_exchangeImplementations(fromMethod, toMethod);
}
#pragma mark Swizzled Method
-(BOOL)crashGuard_hasSuffix:(NSString *)str {
if(!str){
// 打印崩潰信息,棧信息 等
NSLog(@"selector \"hasSuffix\" crash for the the suffix is nil!");
return NO;
} else {
return [self crashGuard_hasSuffix:str];
}
}
- (BOOL)crashGuard_hasPrefix:(NSString *)str {
if(!str){
// 打印崩潰信息,棧信息 等
NSLog(@"selector \"hasPrefix\" crash for the the prefix is nil!");
return NO;
} else {
return [self crashGuard_hasPrefix:str];
}
}
(2)對NSArray的objectAtIndex:的crash預(yù)防如下:
#import "NSArray+CrashGuard.h"
#import <objc/runtime.h>
@implementation NSArray (CrashGuard)
+(void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(crashGuard_objectAtIndex:));
method_exchangeImplementations(fromMethod, toMethod);
});
}
#pragma mark Swizzled Method
-(id)crashGuard_objectAtIndex:(NSUInteger)index {
if(self.count-1 < index) {
// 打印崩潰信息,棧信息 等
NSLog(@"selector \"objectAtIndex\" crash for the index beyond the boundary!");
return nil;
} else {
return [self crashGuard_objectAtIndex:index];
}
}
@end
3)對NSMutableDictionary的setObject:forKey:的crash預(yù)防如下:
#import "NSMutableDictionary+CrashGuard.h"
#import <objc/runtime.h>
@implementation NSMutableDictionary (CrashGuard)
+(void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method fromMethod = class_getInstanceMethod(objc_getClass("__NSDictionaryM"), @selector(setObject:forKey:));
Method toMethod = class_getInstanceMethod(objc_getClass("__NSDictionaryM"), @selector(crashGuard_setObject:forKey:));
method_exchangeImplementations(fromMethod, toMethod);
});
}
#pragma mark Swizzled Method
-(void)crashGuard_setObject:(id)object forKey:(NSString *)key {
if(!object) {
// 打印崩潰信息,棧信息 等
NSLog(@"selector \"setObject:forKey:\" crash for the the object is nil!");
} else {
[self crashGuard_setObject:object forKey:key];
}
}
@end
總結(jié):
Container crash 類型和NSString類型的crash防護方案比較簡單,針對于NSArray/NSMutableArray/NSDictionary/NSMutableDictionary的一些常用的會導致崩潰的API進行method swizzling,然后在swizzle的新方法中加入一些條件限制和判斷,從而讓這些API變的安全。當然,在程序進入這些swizzle的方法后,我們需要及時記錄并上傳相關(guān)的日志信息到后臺服務(wù)器,并及時review相關(guān)的內(nèi)容,將迭代中將有隱患的代碼改掉。