iOS防閃退開(kāi)發(fā)指南

AFNetworking開(kāi)啟removesKeysWithNullValues = YES

  • 好處:可自動(dòng)過(guò)濾后臺(tái)接口返回的null
  • 原因:后臺(tái)返回的null會(huì)通過(guò)NSNull接收,一旦對(duì)NSNull發(fā)消息會(huì)造成閃退


    image.png

使用YYModel接收后臺(tái)接口返回的data

  • 好處:YYModel在Json轉(zhuǎn)Model時(shí),會(huì)自動(dòng)把值為null的字段轉(zhuǎn)為nil,而對(duì)nil發(fā)消息不會(huì)閃退
  • 其他:如果后臺(tái)返回的data嵌套了多層,建議通過(guò)多層YYModel的方法解析Data,避免取到空值。例如,后臺(tái)返回的json中包含兩層Dict,應(yīng)創(chuàng)建兩個(gè)Model分別接收。
  • 推薦寫(xiě)法:
@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
  • 不推薦寫(xiě)法:
@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

通過(guò)宏定義的寫(xiě)法來(lái)判空

原因:宏定義中進(jìn)行了“類(lèi)型判斷”、“null判斷”、“空值判斷”,判空會(huì)更加嚴(yán)謹(jǐn)

  • 推薦寫(xiě)法:
- (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;
    }
    //......
}


  • 不推薦寫(xiě)法:
- (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;
    }
   // ......
}

通過(guò)JKCategories對(duì)NSDictionary進(jìn)行取值和賦值

  • 原因:JKCategories內(nèi)部進(jìn)行了判空和類(lèi)型轉(zhuǎn)換,并采用KVC來(lái)取值和賦值,可以避免閃退
  • 推薦寫(xiě)法:
    //賦值
    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"];
  • 不推薦寫(xiě)法:
    //賦值
    NSString *str = [self getString];
    BOOL boolValue = [self getBool];
    NSDictionary *dict = @{@"str" : str,
                           @"bool" : @(boolValue)};

    //取值
    NSString *str = dict[@"str"];

開(kāi)啟JJException,自動(dòng)攔截閃退。

  • 具體原理可參考:https://github.com/jezzmemo/JJException
  • JJException已Hook掉的系統(tǒng)API可參考:https://github.com/jezzmemo/JJException/blob/master/JJExceptionHookAPI.md
  • 通過(guò)Hook掉系統(tǒng)的API,JJException目前可攔截以下閃退類(lèi)型:
    • Unrecognized Selector Sent to Instance(方法不存在異常)
    • NSNull(方法不存在異常,本質(zhì)上跟Unrecognized Selector一樣)
    • NSString,NSMutableString,NSAttributedString,NSMutableAttributedString(下標(biāo)越界以及參數(shù)nil異常)
    • NSArray,NSMutableArray,NSDictonary,NSMutableDictionary(數(shù)組越界,key-value參數(shù)異常)
    • KVO(忘記移除keypath導(dǎo)致閃退)
    • Zombie Pointer(野指針)
    • NSTimer(忘記移除導(dǎo)致內(nèi)存泄漏)
    • NSNotification(忘記移除導(dǎo)致異常)

通過(guò)MethodSwizzle技術(shù),hook掉系統(tǒng)方法,攔截閃退

  • 原因:對(duì)于JJException無(wú)法攔截的閃退,我們可以通過(guò)自己Hook相關(guān)方法來(lái)實(shí)現(xiàn)閃退攔截。
  • 舉例:
閃退原因:attempt to delete row 1 from section 0 which only contains 1 rows before the update

上述閃退發(fā)生的原因:tableView通過(guò)系統(tǒng)方法reloadRowsAtIndexPaths:withRowAnimation:試圖刷新一個(gè)越界的NSIndexPath。
解決方法:hook掉reloadRowsAtIndexPaths:withRowAnimation:方法,先做判斷,再執(zhí)行。
代碼示例:


@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

第三方閃退(例如高德地圖)

  • 待解決。。。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容