AvoidCrash

該框架使用方法交換,將若干類的常見崩潰方法hook住瘸恼,用try...catch...捕獲Exception異常而避免崩潰奥吩。
比如對數(shù)組NSArray,針對數(shù)組越界錯誤類型构拳,交換方法objectAtIndex:為下面的實現(xiàn)

- (id)__NSArray0AvoidCrashObjectAtIndex:(NSUInteger)index {
    id object = nil;
    @try {
        object = [self __NSArray0AvoidCrashObjectAtIndex:index];
    }
    @catch (NSException *exception) {
        NSString *defaultToDo = AvoidCrashDefaultReturnNil;
        [AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
    }
    @finally {
        return object;
    }
}

@try中若數(shù)組越界了則會在@catch中捕獲異常咆爽,然后處理異常梁棠。因為使用了try...catch,程序不會崩潰斗埂。
對捕獲的異常做處理

+ (void)noteErrorWithException:(NSException *)exception defaultToDo:(NSString *)defaultToDo {
    //堆棧數(shù)據(jù)
    NSArray *callStackSymbolsArr = [NSThread callStackSymbols];
    //獲取在哪個類的哪個方法中實例化的數(shù)組  字符串格式 -[類名 方法名]  或者 +[類名 方法名]
    NSString *mainCallStackSymbolMsg = [AvoidCrash getMainCallStackSymbolMessageWithCallStackSymbols:callStackSymbolsArr];
    
    if (mainCallStackSymbolMsg == nil) {
        mainCallStackSymbolMsg = @"崩潰方法定位失敗,請您查看函數(shù)調(diào)用棧來排查錯誤原因";
    }
    
    NSString *errorName = exception.name;
    NSString *errorReason = exception.reason;
    //errorReason 可能為 -[__NSCFConstantString avoidCrashCharacterAtIndex:]: Range or index out of bounds
    //將avoidCrash去掉
    errorReason = [errorReason stringByReplacingOccurrencesOfString:@"avoidCrash" withString:@""];
    
    NSString *errorPlace = [NSString stringWithFormat:@"Error Place:%@",mainCallStackSymbolMsg];
    
    NSString *logErrorMessage = [NSString stringWithFormat:@"\n\n%@\n\n%@\n%@\n%@\n%@",AvoidCrashSeparatorWithFlag, errorName, errorReason, errorPlace, defaultToDo];
    
    logErrorMessage = [NSString stringWithFormat:@"%@\n\n%@\n\n",logErrorMessage,AvoidCrashSeparator];
    AvoidCrashLog(@"%@",logErrorMessage);
    
    //請忽略下面的賦值符糊,目的只是為了能順利上傳到cocoapods
    logErrorMessage = logErrorMessage;
    
    NSDictionary *errorInfoDic = @{
                                   key_errorName        : errorName,
                                   key_errorReason      : errorReason,
                                   key_errorPlace       : errorPlace,
                                   key_defaultToDo      : defaultToDo,
                                   key_exception        : exception,
                                   key_callStackSymbols : callStackSymbolsArr
                                   };
    //將錯誤信息放在字典里,用通知的形式發(fā)送出去
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:AvoidCrashNotification object:nil userInfo:errorInfoDic];
    });
}

+ (NSString *)getMainCallStackSymbolMessageWithCallStackSymbols:(NSArray<NSString *> *)callStackSymbols {
    //mainCallStackSymbolMsg的格式為   +[類名 方法名]  或者 -[類名 方法名]
    __block NSString *mainCallStackSymbolMsg = nil;
    
    //匹配出來的格式為 +[類名 方法名]  或者 -[類名 方法名]
    NSString *regularExpStr = @"[-\\+]\\[.+\\]";
    
    NSRegularExpression *regularExp = [[NSRegularExpression alloc] initWithPattern:regularExpStr options:NSRegularExpressionCaseInsensitive error:nil];
    
    for (int index = 2; index < callStackSymbols.count; index++) {
        NSString *callStackSymbol = callStackSymbols[index];
        
        [regularExp enumerateMatchesInString:callStackSymbol options:NSMatchingReportProgress range:NSMakeRange(0, callStackSymbol.length) usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
            if (result) {
                NSString* tempCallStackSymbolMsg = [callStackSymbol substringWithRange:result.range];
                
                //get className
                NSString *className = [tempCallStackSymbolMsg componentsSeparatedByString:@" "].firstObject;
                className = [className componentsSeparatedByString:@"["].lastObject;
                NSBundle *bundle = [NSBundle bundleForClass:NSClassFromString(className)];
                
                //filter category and system class
                if (![className hasSuffix:@")"] && bundle == [NSBundle mainBundle]) {
                    mainCallStackSymbolMsg = tempCallStackSymbolMsg;
                }
                *stop = YES;
            }
        }];
        if (mainCallStackSymbolMsg.length) {
            break;
        }
    }
    return mainCallStackSymbolMsg;
}

上面呛凶,[NSThread callStackSymbols]是獲取到當前異常時的堆棧信息男娄,類型是NSArray<NSString *>,例如:

 <_NSCallStackArray 0x604000047500>(
 0   AvoidCrashDemo                      0x0000000109abfab5 +[AvoidCrash noteErrorWithException:defaultToDo:] + 133,
 1   AvoidCrashDemo                      0x0000000109abb6e7 -[NSObject(AvoidCrash) avoidCrashForwardInvocation:] + 311,
 2   CoreFoundation                      0x000000010acc3e08 ___forwarding___ + 760,
 3   CoreFoundation                      0x000000010acc3a88 _CF_forwarding_prep_0 + 120,
 4   AvoidCrashDemo                      0x0000000109aba62a -[ViewController testNoSelectorCrash] + 106,
 5   AvoidCrashDemo                      0x0000000109ab8b49 -[ViewController viewDidLoad] + 73,
 6   UIKit                               0x000000010b3618a5 -[UIViewController loadViewIfRequired] + 1235,
 7   UIKit                               0x000000010b361cf2 -[UIViewController view] + 27,
 8   UIKit                               0x000000010b22fb83 -[UIWindow addRootViewControllerViewIfPossible] + 122,
 9   UIKit                               0x000000010b23028b -[UIWindow _setHidden:forced:] + 294,
 10  UIKit                               0x000000010b243208 -[UIWindow makeKeyAndVisible] + 42,
 )

然后用正則@"[-\+]\[.+\]"匹配出數(shù)組中每一條NSString中的方法漾稀,如-[ViewController testNoSelectorCrash]模闲,得到崩潰產(chǎn)生的方法調(diào)用列表。
將處理后得到的異常信息崭捍,如原因尸折、地址等包裝成一個字典,用通知發(fā)出去殷蛇∈导校可以在AppDelegate中監(jiān)聽通知獲取異常信息,將信息上傳到自己服務器記錄粒梦。

我們一般使用的是Bugly捕獲崩潰信息亮航,但是使用AvoidCrash后異常被捕獲,程序不再崩潰匀们,Bugly不能再捕獲到塞赂,這樣對原本存在的錯誤無法知曉,萬萬不行昼蛀。
發(fā)現(xiàn)Bugly有主動上報異常接口,[Bugly reportException:exception]圆存,so叼旋,只需要將AvoidCrash框架稍微改變一點,讓最后發(fā)通知的時候?qū)⒃镜腅xception也傳遞一下沦辙,再在AppDelegate中監(jiān)聽通知夫植,調(diào)用[Bugly reportException:exception]即可。

AvoidCrash除了能防止數(shù)組越界之類的崩潰之外油讯,還能避免經(jīng)典錯誤unrecognized selector sent to instance,開啟選項后详民,在NSObject+AvoidCrash.m中,hook住消息轉(zhuǎn)發(fā)第三部的那兩個方法

// 消息轉(zhuǎn)發(fā)第三部陌兑,捕獲unrecognized selector sent to instance
- (NSMethodSignature *)avoidCrashMethodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *ms = [self avoidCrashMethodSignatureForSelector:aSelector];
    if (ms == nil) {
      for (NSString *classStr in noneSelClassStrings) {
         if ([self isKindOfClass:NSClassFromString(classStr)]) {
             ms = [AvoidCrashStubProxy instanceMethodSignatureForSelector:@selector(proxyMethod)];
                 break;
          }
      }
    }
    return ms;
}

- (void)avoidCrashForwardInvocation:(NSInvocation *)anInvocation {
    @try {
        [self avoidCrashForwardInvocation:anInvocation]; 
    } @catch (NSException *exception) {
        NSString *defaultToDo = AvoidCrashDefaultIgnore;
        [AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
    } @finally {
    }
}

使用AvoidCrash注意點:
1沈跨、使用[AvoidCrash setupNoneSelClassStringsArr:]]方法時,參數(shù)數(shù)組不能中不能有NSObject兔综,否則會有些奇奇怪怪的bug饿凛,比如彈出鍵盤時會崩潰
2狞玛、數(shù)組中缺少一組交換方法,對象是__NSArrayM涧窒,方法是objectAtIndexedSubscript:

附錄:
16年5月份的時候心肪,那時剛接手別人一個藏家項目,崩潰太多了纠吴,集成Bugly一個版本硬鞍,發(fā)現(xiàn)大多崩潰原因都是諸如數(shù)組越界、數(shù)組字典添加空對象戴已、字符串截取子串NSRange越界等等固该,崩潰之多令人無言以對,逐個排查太費時間恭陡,遂干脆偷懶針對所有的此類bug寫分類蹬音,原理跟AvoidCrash差不多,不過沒有做Exception處理休玩,僅僅是防止崩潰而已著淆。


實現(xiàn)如數(shù)組分類

@implementation NSArray (Exception)

+(void)load{
    SEL originalSelector = @selector(objectAtIndex:);
    SEL swizzledSelector = @selector(by_objectAtIndex:);
    Method originalMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayI"), originalSelector);
    Method swizzledMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayI"), swizzledSelector);
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

- (id)by_objectAtIndex:(NSUInteger)index{
    if (self.count == 0) {
        NSLog(@"%s----數(shù)組為空",__FUNCTION__);
        return nil;
    }
    if (index > self.count - 1) { // 防止數(shù)組越界崩潰
        NSLog(@"%s----數(shù)組越界",__FUNCTION__);
        return nil;
    }
    return [self by_objectAtIndex:index];
}
@end

我看了AvoidCrash第一個版本是16年10月,也許思路還借鑒了我的哈拴疤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末永部,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子呐矾,更是在濱河造成了極大的恐慌苔埋,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜒犯,死亡現(xiàn)場離奇詭異组橄,居然都是意外死亡,警方通過查閱死者的電腦和手機罚随,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門玉工,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人淘菩,你說我怎么就攤上這事遵班。” “怎么了潮改?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵狭郑,是天一觀的道長。 經(jīng)常有香客問我汇在,道長翰萨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任糕殉,我火速辦了婚禮缨历,結(jié)果婚禮上以蕴,老公的妹妹穿的比我還像新娘。我一直安慰自己辛孵,他們只是感情好丛肮,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著魄缚,像睡著了一般宝与。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上冶匹,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天习劫,我揣著相機與錄音,去河邊找鬼嚼隘。 笑死诽里,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的飞蛹。 我是一名探鬼主播谤狡,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼卧檐!你這毒婦竟也來了墓懂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤霉囚,失蹤者是張志新(化名)和其女友劉穎捕仔,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盈罐,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡榜跌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了盅粪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斜做。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖湾揽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情笼吟,我是刑警寧澤库物,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站贷帮,受9級特大地震影響戚揭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜撵枢,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一民晒、第九天 我趴在偏房一處隱蔽的房頂上張望精居。 院中可真熱鬧,春花似錦潜必、人聲如沸靴姿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽佛吓。三九已至,卻和暖如春垂攘,著一層夾襖步出監(jiān)牢的瞬間维雇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工晒他, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吱型,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓陨仅,卻偏偏與公主長得像津滞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子掂名,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法据沈,類相關(guān)的語法,內(nèi)部類的語法饺蔑,繼承相關(guān)的語法锌介,異常的語法,線程的語...
    子非魚_t_閱讀 31,581評論 18 399
  • 通俗編程——白話JAVA異常機制 - 代碼之道猾警,編程之法 - 博客頻道 - CSDN.NEThttp://blog...
    葡萄喃喃囈語閱讀 3,165評論 0 25
  • 源碼地址: AvoidCrash https://github.com/chenfanfang/AvoidC...
    chenfanfang閱讀 11,257評論 45 80
  • 初識異常(Exception) 比如我們在取數(shù)組里面的某個值得時候孔祸,經(jīng)常會出現(xiàn)定義的取值范圍超過了數(shù)組的大小,那么...
    iDaniel閱讀 1,864評論 1 2
  • 因為公司新產(chǎn)品近期要上線,所以上周末我兩天都要加班穴墅,我不知道對于其他的加班族是一種什么感受惶室,反正當我在那個周五聽到...
    達達令閱讀 747評論 1 2