級別: ★★☆☆☆
標(biāo)簽:「iOS 」「避免常見崩潰」
作者: WYW
審校: QiShare團(tuán)隊(duì)
筆者最近看了部分引起App Crash的常見情況疯潭,這次先討論下操作集合類型(如NSArray郁稍,NSDictionary等)時(shí)门躯,防止常見崩潰(如避免從數(shù)組中取值時(shí)越界、往字典中插入為nil的value等)的內(nèi)容殉摔。
- 為了避免崩潰凄贩,操作集合類對象時(shí)淑玫,設(shè)置值和取值的時(shí)候崎岂,可以考慮使用如下方法:
- 使用
分類添加的安全性方法
- 使用
- 使用
交換系統(tǒng)方法和我們做了安全性處理的方法
- 使用
安全操作集合類對象:添加分類方法
當(dāng)操作集合類對象的時(shí)候捆毫,可以使用我們添加的安全取值的分類。
添加分類方法部分冲甘,先從NSString* 和NSNumber* 的integerValue談起冻璃。
- (1)如在NSObject的分類中添加的qi_safeIntegerValue用于替換平時(shí)用的integerValue的方法。
- (NSInteger)qi_safeIntegerValue {
if ([self isKindOfClass:[NSNumber class]]) {
return [((NSNumber *)self) integerValue];
} else if([self isKindOfClass:[NSString class]]) {
return [((NSString *)self) integerValue];
} else {
return kCustomErrorCode;
}
}
使用qi_safeIntegerValue的方式為:
id number = @(1);
[number qi_safeIntegerValue];
- (2)如在NSObject的分類中添加了qi_safeArrayObjectAtIndex用于替換NSArray* 的
- (ObjectType)objectAtIndex:(NSUInteger)index;
- (id)qi_safeArrayObjectAtIndex:(NSUInteger)index {
if (![self isKindOfClass:[NSArray class]]) {
return nil;
}
if (index < 0 || index >= ((NSArray *)self).count) {
return nil;
}
return [(NSArray *)self objectAtIndex:index];
}
使用qi_safeArrayObjectAtIndex的方式為:
NSArray *qiArr = @[@1];
[qiArr qi_safeArrayObjectAtIndex:0];
[qiArr qi_safeArrayObjectAtIndex:1];
- 更多相關(guān)內(nèi)容损合,可查看Demo QiSafeType。
不過只使用分類娘纷,不適于我們使用字面量語法取值的情況嫁审。如對于數(shù)組來說,如果我們想要使用
qiArr[0]
這樣的字面量語法取值赖晶,使用當(dāng)前的分類的方式就不適用了律适。此時(shí)就需要結(jié)合runtime使用交換qiArr[0]調(diào)用的系統(tǒng)方法和我們自己添加的安全取值的方法
來達(dá)到字面量安全取值的目的。
安全操作集合類對象:方法交換
安全操作集合類對象遏插,方法交換部分捂贿,筆者會以字面量操作NSArray聊聊相關(guān)內(nèi)容。
以字面量的方式聲明數(shù)組 及取值:
// 聲明數(shù)組的時(shí)候 插入nil
NSString *nilValue = nil;
NSArray *qiArr = @[@"qishare0", nilValue, @"qiShare2"];
NSString *nilValue = nil;
NSArray *qiArr = @[@"qishare0", nilValue, @"qiShare2"];
NSLog(@"qiArr:%@", qiArr);
// 從數(shù)組中取值 故意越界取值
NSLog(@"qiArr[0]:%@ qiArr[1]:%@ qiArr[2]:%@",qiArr[0],qiArr[1],qiArr[2]);
// 輸出結(jié)果如下:
qiArr:(
qishare0,
qiShare2
)
qiArr[0]:qishare0 qiArr[1]:qiShare2 qiArr[2]:(null)
- 交換方法的操作需要引入
#import <objc/runtime.h>
胳嘲,主要會用到下邊的API厂僧。
// Returns a pointer to the data structure describing a given class method for a given class.
// 返回一個(gè)描述指定類cls和給定類方法的數(shù)據(jù)結(jié)構(gòu)的指針 其實(shí)返回值也是一個(gè)Method
// 獲取指定類Cls和指定方法sel的對應(yīng)的 類 Method
Method class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
// Returns a specified instance method for a given class.
// 返回指定的類cls及相應(yīng)selector的實(shí)例方法
// 獲取指定類Cls和指定方法sel的對應(yīng)的 實(shí)例 Method
Method class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
// 交換Method m1和Method m2的實(shí)現(xiàn)
void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
- 交換方法需要待交換方法
SEL
,及待交換的類Class
以下內(nèi)容筆者會以字面量的方式聲明qiArr
NSArray *qiArr = @[@"qishare0", nilValue, @"qiShare2"];
為例進(jìn)行分析了牛,所需的class及要交換的SEL
- (1)待交換的方法
就待交換的方法而言颜屠,聲明qiArr的時(shí)候。首先要確定調(diào)用的NSArray哪個(gè)方法鹰祸。調(diào)用的是
+ (instancetype)arrayWithObjects:(const ObjectType _Nonnull [_Nonnull])objects count:(NSUInteger)cnt;
甫窟;這個(gè)可以通過自己寫一個(gè)數(shù)組的子類來證實(shí)這件事,也可以通過交換方法后調(diào)用的方法來反面驗(yàn)證蛙婴。NSArray的子類的寫法可以參考QiSafeType中的QiSubArray的寫法粗井。
關(guān)于繼承NSArray:Any subclass of NSArray must override the primitive instance methods
count
andobjectAtIndex:
. These methods must operate on the backing store that you provide for the elements of the collection. For this backing store you can use a static array, a standard NSArray object, or some other data type or mechanism. You may also choose to override, partially or fully, any other NSArray method for which you want to provide an alternative implementation.
+ (instancetype)arrayWithObjects:(const ObjectType _Nonnull [_Nonnull])objects count:(NSUInteger)cnt;
是一個(gè)類方法,所以方法交換部分,筆者也寫了一個(gè)安全聲明數(shù)組的類方法浇衬。+ (instancetype)qisafeArrayWithObjects:(const id _Nonnull [_Nonnull])objects count:(NSUInteger)cnt
筆者寫的待交換的+ (instancetype)qisafeArrayWithObjects:(const id _Nonnull [_Nonnull])objects count:(NSUInteger)cnt
的方法體如下:
+ (instancetype)qisafeArrayWithObjects:(const id _Nonnull [_Nonnull])objects count:(NSUInteger)cnt {
id instance = nil;
id safeObjs[cnt];
NSUInteger j = 0;
for (NSUInteger i = 0; i < cnt; i ++) {
if (!objects[i]) {
continue;
}
safeObjs[j++] = objects[i];
}
instance = [self qisafeArrayWithObjects:safeObjs count:j];
return instance;
}
思想:在聲明字面量數(shù)組的時(shí)候懒构,先遍歷指定的數(shù)組,過濾掉為空的對象径玖,剩余的存放到另一個(gè)數(shù)組中痴脾。再調(diào)用自己添加的聲明數(shù)組的類方法(因?yàn)槲覀兲砑拥姆椒ê拖到y(tǒng)的方法進(jìn)行了方法交換,所以這里實(shí)質(zhì)是調(diào)用的系統(tǒng)的聲明數(shù)組的方法梳星。)
- (2)待交換的類
聲明qiArr的時(shí)候待交換的方法為類方法赞赖,所以待交換的類為[NSArray class]
就待交換的方法而言。以qiArr[0]為例冤灾。要確定調(diào)用的數(shù)組的那個(gè)方法前域。調(diào)用的是
- (ObjectType)objectAtIndex:(NSUInteger)index;
這個(gè)可以通過自己寫一個(gè)數(shù)組的子類來證實(shí)這件事,也可以通過交換方法后調(diào)用的方法來反面驗(yàn)證韵吨。
上述問題明確了之后匿垄,我們就可以在NSArray的分類的+ (void)load
方法中根據(jù)指定的類[NSArray class]把+ (instancetype)arrayWithObjects:(const ObjectType _Nonnull [_Nonnull])objects count:(NSUInteger)cnt;
和 + (instancetype)qisafeArrayWithObjects:(const id _Nonnull [_Nonnull])objects count:(NSUInteger)cnt
進(jìn)行方法交換了。
關(guān)鍵代碼如下:
Method originMethod = class_getClassMethod([NSArray class], @selector(arrayWithObjects:count:));
Method alterMethod = class_getClassMethod([NSArray class], @selector(qisafeArrayWithObjects:count:));
method_exchangeImplementations(originMethod, alterMethod);
以上部分归粉,筆者解釋了椿疗,NSArray以字面量的方式聲明數(shù)組的方法交換部分的內(nèi)容,下邊筆者將繼續(xù)和大家聊一下關(guān)于NSArray字面量取值的內(nèi)容糠悼。其實(shí)NSArray運(yùn)作在抽象工廠設(shè)計(jì)模式下届榄。抽象工廠模式是類簇在iOS下的的一種實(shí)現(xiàn)。眾多常用類倔喂,如NSString铝条,NSArray,NSDictionary席噩,NSNumber都運(yùn)作在抽象工廠模式下班缰,引自從NSArray看類簇
以下部分,大家也可以直接看Demo中的寫法自行分析悼枢。
筆者想通過以下代碼說明埠忘,創(chuàng)建出的NSArray*的實(shí)例(qiArr)的class,和創(chuàng)建qiArr的方式及qiArr中包含的對象的個(gè)數(shù)有關(guān)馒索。
NSString *nilValue = nil;
// 聲明數(shù)組的時(shí)候 插入nil
NSArray *qiArr = @[@"qishare0", nilValue, @"qiShare2"];
NSLog(@"qiArr:%@", qiArr);
NSLog(@"qiArr[0]:%@ qiArr[1]:%@ qiArr[2]:%@",qiArr[0],qiArr[1],qiArr[2]);
id tempValue = nil;
NSArray *qiArr0 = @[];
tempValue = qiArr0[0];
tempValue = qiArr0[1];
NSArray *qiArr1 = @[@1];
tempValue = qiArr1[0];
tempValue = qiArr1[1];
NSArray *qiArr2 = @[@1, @2];
tempValue = qiArr2[1];
tempValue = qiArr2[2];
NSLog(@"qiArr0 class:%@", NSStringFromClass([qiArr0 class]));
NSLog(@"qiArr1 class:%@", NSStringFromClass([qiArr1 class]));
NSLog(@"qiArr2 class:%@", NSStringFromClass([qiArr2 class]));
NSArray *qiArr3 = [NSArray arrayWithObjects:@"1", @"2", @"3", nil];
NSLog(@"qiArr3 class:%@", NSStringFromClass([qiArr3 class]));
qiArr0 class:__NSArray0
qiArr1 class:__NSSingleObjectArrayI
qiArr2 class:__NSArrayI
qiArr3 class:__NSArrayI
有了上述內(nèi)容给梅,我們就可以在通過字面量語法故意越界訪問數(shù)組對象的時(shí)候,根據(jù)崩潰的提示双揪,添加上相應(yīng)的方法交換动羽。
關(guān)鍵代碼如下:
// __NSArray0
Method originArr0ObjectAtIndexMethod = class_getInstanceMethod(NSClassFromString(@"__NSArray0"), @selector(objectAtIndex:));
Method alterArr0ObjectAtIndexMethod = class_getInstanceMethod(NSClassFromString(@"__NSArray0"), @selector(qiSafeArr0ObjectAtIndex:));
method_exchangeImplementations(originArr0ObjectAtIndexMethod, alterArr0ObjectAtIndexMethod);
// __NSSingleObjectArrayI
Method originSingleObjArrIObjectAtIndexMethod = class_getInstanceMethod(NSClassFromString(@"__NSSingleObjectArrayI"), @selector(objectAtIndex:));
Method alterSingleObjArrIObjectAtIndexMethod = class_getInstanceMethod(NSClassFromString(@"__NSSingleObjectArrayI"), @selector(qiSafeSingleObjArrIObjectAtIndex:));
method_exchangeImplementations(originSingleObjArrIObjectAtIndexMethod, alterSingleObjArrIObjectAtIndexMethod);
// 注意__NSArrayI調(diào)用字面量語法的時(shí)候調(diào)用的系統(tǒng)方法為`- (ObjectType)objectAtIndexedSubscript:(NSUInteger)idx API_AVAILABLE(macos(10.8), ios(6.0), watchos(2.0), tvos(9.0));` 和上述類型的NSArray待交換的方法不同,這一點(diǎn)可以故意測試崩潰渔期,查看崩潰的提示得出运吓。
// __NSArrayI
Method originArrIObjAtIndexedSubMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayI"), @selector(objectAtIndexedSubscript:));
Method alterArrIObjAtIndexedSubMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayI"), @selector(qiSafeArrIObjAtIndexedSubscript:));
method_exchangeImplementations(originArrIObjAtIndexedSubMethod, alterArrIObjAtIndexedSubMethod);
Demo
- 更多相關(guān)內(nèi)容渴邦,可查看Demo QiSafeType。
參考學(xué)習(xí)網(wǎng)址
推薦文章:
iOS消息轉(zhuǎn)發(fā)
iOS 自定義拖拽式控件:QiDragView
iOS 自定義卡片式控件:QiCardView
iOS Wireshark抓包
iOS Charles抓包
初探TCP
IP拘哨、UDP初探