iOS防闪退开发指南

AFNetworking开启removesKeysWithNullValues = YES

  • 好处:可自动过滤后台接口返回的null
  • 原因:后台返回的null会通过NSNull接收,一旦对NSNull发消息会造成闪退


    iOS防闪退开发指南_第1张图片
    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 *)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

第三方闪退(例如高德地图)

  • 待解决。。。

你可能感兴趣的:(iOS防闪退开发指南)