iOS防闪退开发指南
2019-07-24 本文已影响120人
Aaron_ZhangKH
AFNetworking开启removesKeysWithNullValues = YES
- 好处:可自动过滤后台接口返回的null
-
原因:后台返回的null会通过NSNull接收,一旦对NSNull发消息会造成闪退
image.png
使用YYModel接收后台接口返回的data
- 好处:YYModel在Json转Model时,会自动把值为null的字段转为nil,而对nil发消息不会闪退
- 其他:如果后台返回的data嵌套了多层,建议通过多层YYModel的方法解析Data,避免取到空值。例如,后台返回的json中包含两层Dict,应创建两个Model分别接收。
- 推荐写法:
@interface HR_Applicant : MP_BaseModel
@property (nonatomic,copy ) NSString *applicantId;
@property (nonatomic,copy ) NSString *name;
@property (nonatomic,strong) HR_Resume *resume;
@end
@interface HR_Resume : MP_BaseModel
@property (nonatomic,copy ) NSString *birthYear;
@property (nonatomic,copy ) NSString *imageUrls;
@end
- 不推荐写法:
@interface HR_Applicant : MP_BaseModel
@property (nonatomic,copy ) NSString *applicantId;
@property (nonatomic,copy ) NSString *name;
@property (nonatomic,strong) NSDictionary *resume;
@end
@implementation HR_Applicant
- (void)parseResumeData{
NSDictionary *responseDict = [self responseObject];
NSDictionary *resume = responseDict[@"resume"];
self.resume = resume;
}
@end
通过宏定义的写法来判空
原因:宏定义中进行了“类型判断”、“null判断”、“空值判断”,判空会更加严谨
- 推荐写法:
- (void)demo{
NSString *str = [self getString];
if (kStringIsEmpty(str)) {
return;
}
NSArray *arr = [self getArray];
if (kArrayIsEmpty(arr)) {
return;
}
NSDictionary *dict = [self getDict];
if (kDictIsEmpty(dict)) {
return;
}
//......
}
- 不推荐写法:
- (void)demo{
NSString *str = [self getString];
if (str.length == 0) {
return;
}
NSArray *arr = [self getArray];
if (arr.count == 0) {
return;
}
NSDictionary *dict = [self getDict];
if (dict.allKeys.count == 0) {
return;
}
// ......
}
通过JKCategories对NSDictionary进行取值和赋值
- 原因:JKCategories内部进行了判空和类型转换,并采用KVC来取值和赋值,可以避免闪退
- 推荐写法:
//赋值
NSString *str = [self getString];
BOOL boolValue = [self getBool];
NSMutableDictionary *dictM = [NSMutableDictionary dictionary];
[dictM jk_setString:str forKey:@"str"];
[dictM jk_setBool:boolValue forKey:@"bool"];
//取值
NSString *str = [dict jk_stringForKey:@"str"];
- 不推荐写法:
//赋值
NSString *str = [self getString];
BOOL boolValue = [self getBool];
NSDictionary *dict = @{@"str" : str,
@"bool" : @(boolValue)};
//取值
NSString *str = dict[@"str"];
开启JJException,自动拦截闪退。
- 具体原理可参考:https://github.com/jezzmemo/JJException
- JJException已Hook掉的系统API可参考:https://github.com/jezzmemo/JJException/blob/master/JJExceptionHookAPI.md
- 通过Hook掉系统的API,JJException目前可拦截以下闪退类型:
- Unrecognized Selector Sent to Instance(方法不存在异常)
- NSNull(方法不存在异常,本质上跟Unrecognized Selector一样)
- NSString,NSMutableString,NSAttributedString,NSMutableAttributedString(下标越界以及参数nil异常)
- NSArray,NSMutableArray,NSDictonary,NSMutableDictionary(数组越界,key-value参数异常)
- KVO(忘记移除keypath导致闪退)
- Zombie Pointer(野指针)
- NSTimer(忘记移除导致内存泄漏)
- NSNotification(忘记移除导致异常)
通过MethodSwizzle技术,hook掉系统方法,拦截闪退
- 原因:对于JJException无法拦截的闪退,我们可以通过自己Hook相关方法来实现闪退拦截。
- 举例:
闪退原因:attempt to delete row 1 from section 0 which only contains 1 rows before the update
上述闪退发生的原因:tableView通过系统方法reloadRowsAtIndexPaths:withRowAnimation:
试图刷新一个越界的NSIndexPath。
解决方法:hook掉reloadRowsAtIndexPaths:withRowAnimation:
方法,先做判断,再执行。
代码示例:
@implementation UITableView (FixCrash)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getInstanceMethod(self.class, @selector(reloadRowsAtIndexPaths:withRowAnimation:));
Method swizzleMethod = class_getInstanceMethod(self.class, @selector(safeReloadRowsAtIndexPaths:withRowAnimation:));
method_exchangeImplementations(originalMethod, swizzleMethod);
});
}
- (void)safeReloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation{
NSUInteger totalSectionCount = self.numberOfSections;
for (NSIndexPath *indexPath in indexPaths) {
if (indexPath.section >= totalSectionCount) {
return;
}
NSUInteger totalRowCount = [self numberOfRowsInSection:indexPath.section];
if (indexPath.row >= totalRowCount) {
return;
}
}
[self safeReloadRowsAtIndexPaths:indexPaths withRowAnimation:animation];
}
@end
第三方闪退(例如高德地图)
- 待解决。。。