問題的產(chǎn)生
NSString *string = nil;
// 不可變數(shù)組
NSArray *array = @[string]; // 初始化中有nil對象
// 可變數(shù)組
NSMutableArray *array2 = [NSMutableArray array];
[array2 addObject:string]; // 添加nil對象
// 不可變字典
NSDictionary *dic = @{@"key":string};
// 可變字典
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
[dic setObject:string forKey:@"key"]; // 設(shè)置nil對象
上述的幾個例子中,都是對數(shù)組绊谭、字典的異常操作政恍,因?yàn)樵刂卸汲霈F(xiàn)了nil對象,雖然我們可以在添加之前加判斷去除為nil的情況达传,但是如果內(nèi)容很多篙耗,勢必會很繁瑣,如果有更好的辦法幫我們做完這些繁瑣的事情豈不是美事趟大?
項(xiàng)目中的問題
項(xiàng)目中可能有很多類似下面的寫法
NSArray *array = @[string]; // 初始化中有nil對象
NSDictionary *dic = @{@"key":string};
有的添加了三目運(yùn)算符鹤树,去掉了元素為nil的情況,但是非常的麻煩逊朽,有時候甚至?xí)浐辈@就埋下了很多隱患。
解決方案
- 繼承
如果項(xiàng)目中有父類的存在叽讳,我們可以在父類中做些文章追他,我們可以一些新增數(shù)據(jù)操作方法,用來過濾掉一些異常操作(比如跳過nil對象部分)
- 分類
方案一顯然是不理想的岛蚤,因?yàn)轫?xiàng)目中可能存在多種父類邑狸,情況多變復(fù)雜,顯然操作性太低
采用分類方式涤妒,分別新增NSArray单雾,NSDictionary等分類文件,為其新增操作方法她紫,在方法中過濾掉異常操作
- 運(yùn)行時
方案二較方案一有了更高的操作性硅堆,可行性,一定程度上解決了異常操作問題贿讹,但是依舊存在著不少問題渐逃,例如,
我們添加分類后民褂,我們以后就必須使用新增的方法來操作數(shù)據(jù)茄菊,對于之前的舊代碼依舊未能作出響應(yīng),假如全部替換的話赊堪,勢必會產(chǎn)生不小的工作量面殖,這不是我們想看到的;
另外哭廉,@[],@{}這種方式將不再可用脊僚,不,系統(tǒng)的部分操作方法都不可用群叶,局限性還是很大的
那么吃挑,有沒有更為優(yōu)雅的方式解決上述問題呢?答案是有的街立,就是使用我們OC強(qiáng)大的運(yùn)行時
基本思路:
1舶衬、使用分類
2、在 + (void)load赎离;方法中進(jìn)行方法交換
3逛犹、在自己的方法中處理掉異常
具體實(shí)現(xiàn)
例子1:addObject方法添加nil對象
我們先寫一個異常操作
NSString *string = nil;
NSMutableArray *array = [NSMutableArray array];
[array addObject:string];
錯誤提示
我們可以看到,__NSArrayM 對象調(diào)用了 -insertObject:atIndex:
產(chǎn)生了object cannot be nil的錯誤梁剔,顯然易見虽画,addObject方法最終會調(diào)用 -insertObject:atIndex:
方法,而對象不能為nil 荣病。
接下來我們來使用運(yùn)行時交換方法码撰,處理掉這種情況
@implementation NSMutableArray (safe)
+(void)load{
[self swizze];
}
+(void)swizze{
Method old = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(insertObject:atIndex:));
Method new = class_getInstanceMethod(self, @selector(insertObject_safe:atIndex:));
if (!old || !new) {
return;
}
method_exchangeImplementations(old, new); // 交換方法
}
-(void)insertObject_safe:(id)anObject atIndex:(NSUInteger)index{
if (index > self.count || !anObject) {
return; // 過濾到異常部分
}
[self insertObject_safe:anObject atIndex:index];
}
@end
將該分類導(dǎo)入需要的文件中,array添加對象時就不會在出現(xiàn)crash問題了个盆。
例子2:數(shù)組越界
我們使用不可變數(shù)組做例子
NSString *string = nil;
NSArray *array = @[@"0",@"1",@"2"];
NSLog(@"%@",array[5]);
報(bào)錯情況
對象__NSArrayI調(diào)用objectAtIndex:出現(xiàn)了越界脖岛。
同樣的
@implementation NSArray (safe)
+(void)load{
[self swizze];
}
+(void)swizze{
Method old = class_getInstanceMethod(NSClassFromString(@"__NSArrayI"), @selector(objectAtIndex:));
Method new = class_getInstanceMethod(self, @selector(objectAtIndex_safe:));
if (!old || !new) {
return;
}
method_exchangeImplementations(old, new); // 交換方法
}
-(id)objectAtIndex_safe:(NSUInteger)index{
if (index>=self.count) {
return nil; // 處理異常部分
}
return [self objectAtIndex_safe:index];
}
@end
運(yùn)行,輸出
我們看到颊亮,當(dāng)數(shù)組越界時柴梆,僅僅是返回了 nil。
除了上述兩個例子终惑,系統(tǒng)中還有很多異常操作绍在,比如數(shù)組的插入,替換雹有,字典的setObject偿渡、字符串的操作、NSRange等等件舵,都是待處理的部分卸察。
總結(jié)
相比于在分類中新增方法,使用運(yùn)行時捕獲對應(yīng)方法铅祸,會更優(yōu)雅坑质,我們不必再需要大張旗鼓的使用新方法替換舊項(xiàng)目中的系統(tǒng)方法,一勞永逸
優(yōu)化部分
因?yàn)槲覀冞^濾了異常部分临梗,無法定位錯誤涡扼,我們調(diào)試起來異常困難,為此盟庞,這種過濾方式最好僅僅在release模式下產(chǎn)生作用吃沪,而debug模式下依舊需要crash,這點(diǎn)可以使用宏來控制什猖,也可以使用NSAssert斷言來控制
寫在最后
使用cocoaPods導(dǎo)入相關(guān)框架
pod 'SafeKit'