iOS NSException崩潰處理

本文主要介紹如何防止Foundation當中的常見崩潰處理

Demo地址:KJExtensionHandler
熟悉又討厭的崩潰
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]'
*** First throw call stack:
(
    0   CoreFoundation                      0x0000000103dca126 __exceptionPreprocess + 242
    1   libobjc.A.dylib                     0x0000000103c54f78 objc_exception_throw + 48
    2   CoreFoundation                      0x0000000103e46cdb _CFThrowFormattedException + 194
    3   CoreFoundation                      0x0000000103e5221e -[__NSPlaceholderDictionary initWithCapacity:].cold.1 + 0
    4   CoreFoundation                      0x0000000103e351f7 -[__NSPlaceholderDictionary initWithObjects:forKeys:count:] + 227
    5   CoreFoundation                      0x0000000103dc8da3 +[NSDictionary dictionaryWithObjects:forKeys:count:] + 49
    6   KJExtensionHandler                  0x00000001033b715f -[ViewController viewDidLoad] + 815
    7   UIKitCore                           0x000000010d7ac73b -[UIViewController _sendViewDidLoadWithAppearanceProxyObjectTaggingEnabled] + 88
    8   UIKitCore                           0x000000010d7b1022 -[UIViewController loadViewIfRequired] + 1084
    9   UIKitCore                           0x000000010d6e800e -[UINavigationController _updateScrollViewFromViewController:toViewController:] + 162
    10  UIKitCore                           0x000000010d6e82f8 -[UINavigationController _startTransition:fromViewController:toViewController:] + 154
    11  UIKitCore                           0x000000010d6e9371 -[UINavigationController _startDeferredTransitionIfNeeded:] + 851
    12  UIKitCore                           0x000000010d6ea6dc -[UINavigationController __viewWillLayoutSubviews] + 150
    13  UIKitCore                           0x000000010d6caf1e -[UILayoutContainerView layoutSubviews] + 217
    14  UIKitCore                           0x000000010e43d9ce -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2874
    15  QuartzCore                          0x0000000105546d87 -[CALayer layoutSublayers] + 258
    16  QuartzCore                          0x000000010554d239 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 575
    17  QuartzCore                          0x0000000105558f91 _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 65
    18  QuartzCore                          0x0000000105499078 _ZN2CA7Context18commit_transactionEPNS_11TransactionEdPd + 496
    19  QuartzCore                          0x00000001054cfe13 _ZN2CA11Transaction6commitEv + 783
    20  UIKitCore                           0x000000010defe27a __34-[UIApplication _firstCommitBlock]_block_invoke_2 + 81
    21  CoreFoundation                      0x0000000103d385db __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
    22  CoreFoundation                      0x0000000103d379ef __CFRunLoopDoBlocks + 434
    23  CoreFoundation                      0x0000000103d3240c __CFRunLoopRun + 899
    24  CoreFoundation                      0x0000000103d31b9e CFRunLoopRunSpecific + 567
    25  GraphicsServices                    0x000000010c7ebdb3 GSEventRunModal + 139
    26  UIKitCore                           0x000000010dee0af3 -[UIApplication _run] + 912
    27  UIKitCore                           0x000000010dee5a04 UIApplicationMain + 101
    28  KJExtensionHandler                  0x00000001033ea92a main + 122
    29  libdyld.dylib                       0x00000001065eb415 start + 1
    30  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
防崩處理之後的效果
------- 😎 給我點贊 😎 -------
編譯時間:10:18:10
文件名:KJExceptionTool.m
方法名:+[KJExceptionTool kj_crashDealWithException:CrashTitle:]
行號:42
打印信息:========== crash 日誌 ==========
crashName: NSInvalidArgumentException
crashTitle: Exception handling remove nil key-values and instance a dictionary: 字典賦值存在空
key:(null), val:123
key:key, val:(null)

crashReason: *** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]
crashMessage: -[ViewController viewDidLoad]

簡單介紹異常處理工具KJExceptionTool

方法一:開啓全部方法交換
/// 開啓全部方法交換
+ (void)kj_openAllExchangeMethod{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [NSArray kj_openExchangeMethod];
        [NSMutableArray kj_openExchangeMethod];
        [NSDictionary kj_openExchangeMethod];
        [NSMutableDictionary kj_openExchangeMethod];
        [NSMutableString kj_openExchangeMethod];
    });
}

/// 單個使用
[NSArray kj_openExchangeMethod];
@protocol KJExceptionProtocol <NSObject>
@required
/// 開啓方法交換
+ (void)kj_openExchangeMethod;

@end
方法二:異常回調處理
static kExceptionBlock _exceptionblock = nil;
+ (kExceptionBlock)exceptionblock{return _exceptionblock;}
+ (void)setExceptionblock:(kExceptionBlock)exceptionblock{
    _exceptionblock = exceptionblock;
}
/// 異常回調處理
+ (void)kj_crashBlock:(kExceptionBlock)block{
    self.exceptionblock = block;
}

備註:這個只需要在最開始的地方調用一次即可

方法三:異常獲取
/// 異常獲取
+ (void)kj_crashDealWithException:(NSException*)exception CrashTitle:(NSString*)title{
    NSString *crashMessage = [self kj_analysisCallStackSymbols:[NSThread callStackSymbols]];
    if (crashMessage == nil) crashMessage = @"崩潰方法定位失敗,請查看函數調用棧來排查錯誤原因";
    NSString *crashName   = exception.name;
    NSString *crashReason = exception.reason;
    crashReason = [crashReason stringByReplacingOccurrencesOfString:@"avoidCrash" withString:@""];
    NSLog(@"========== crash 日誌 ==========\ncrashName: %@\ncrashTitle: %@\ncrashReason: %@\ncrashMessage: %@",crashName,title,crashReason,crashMessage);
    if (self.exceptionblock) {
        NSDictionary *dict = @{@"crashName":crashName,
                               @"crashReason":crashReason,
                               @"crashTitle":title,
                               @"crashMessage":crashMessage,
                               @"exception":exception,
                               @"callStackSymbols":[NSThread callStackSymbols]
        };
        _weakself;
        kGCD_main(^{weakself.exceptionblock(dict);});
    }
}
/// 解析異常消息
+ (NSString*)kj_analysisCallStackSymbols:(NSArray<NSString*>*)callStackSymbols{
    __block NSString *msg = nil;
    NSString *pattern = @"[-\\+]\\[.+\\]";/// 匹配出來的格式爲 +[類名 方法名] 或者 -[類名 方法名]
    NSRegularExpression *regularExp = [[NSRegularExpression alloc] initWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil];
    for (NSInteger i = 2; i < callStackSymbols.count; i++) {
        NSString *matchesString = callStackSymbols[i];
        [regularExp enumerateMatchesInString:matchesString options:NSMatchingReportProgress range:NSMakeRange(0, matchesString.length) usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
            if (result) {
                NSString *tempMsg = [matchesString substringWithRange:result.range];
                NSString *className = [tempMsg componentsSeparatedByString:@" "].firstObject;
                className = [className componentsSeparatedByString:@"["].lastObject;
                if (![className hasSuffix:@")"] && [NSBundle bundleForClass:NSClassFromString(className)] == [NSBundle mainBundle]) {
                    msg = tempMsg;
                }
                *stop = YES;
            }
        }];
        if (msg.length) break;
    }
    return msg;
}

簡單介紹一個分類當中的異常防崩潰處理

是否開啓方法交換

+ (void)kj_openExchangeMethod{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        /// 越界崩潰方式一:[array objectAtIndex:0];
        [objc_getClass("__NSArrayI") kj_swizzleMethod:@selector(objectAtIndex:) Method:@selector(kj_objectAtIndex:)];
        /// 越界崩潰方式二:array[0];
        [objc_getClass("__NSArrayI") kj_swizzleMethod:@selector(objectAtIndexedSubscript:) Method:@selector(kj_objectAtIndexedSubscript:)];
    });
}

交換之後的處理

- (instancetype)kj_objectAtIndex:(NSUInteger)index{
    NSArray *temp = nil;
    @try {
        temp = [self kj_objectAtIndex:index];
    }@catch (NSException *exception) {
        NSString *string = @"Exception handling return nil to avoid crash: ";
        if (self.count == 0) {
            string = [string stringByAppendingString:@"數組個數爲零"];
        }else if (self.count <= index) {
            string = [string stringByAppendingString:@"數組索引越界"];
        }
        [KJExceptionTool kj_crashDealWithException:exception CrashTitle:string];
    }@finally {
        return temp;
    }
}

- (instancetype)kj_objectAtIndexedSubscript:(NSUInteger)index{
    NSArray *temp = nil;
    @try {
        temp = [self kj_objectAtIndexedSubscript:index];
    }@catch (NSException *exception) {
        NSString *string = @"Exception handling return nil to avoid crash: ";
        if (self.count == 0) {
            string = [string stringByAppendingString:@"數組個數爲零"];
        }else if (self.count <= index) {
            string = [string stringByAppendingString:@"數組索引越界"];
        }
        [KJExceptionTool kj_crashDealWithException:exception CrashTitle:string];
    }@finally {
        return temp;
    }
}

使用介紹

[KJExceptionTool kj_openAllExchangeMethod];
[KJExceptionTool kj_crashBlock:^BOOL(NSDictionary * _Nonnull dict) {
    NSLog(@"回調處理:\n%@", dict[@"crashTitle"]);
    return YES;
}];
[self.sectionTemps objectAtIndex:3];
NSMutableArray *temp = [NSMutableArray arrayWithObjects:@"1",@"2",@"3",nil];
NSString *str = nil;
[temp addObject:str];
[temp setObject:@"1" atIndexedSubscript:4];
[temp insertObject:str atIndex:4];
NSDictionary *dicX = @{str:@"123",
                       @"key":str,
                       @"key":@"1"
};
NSMutableDictionary *dict = [[NSMutableDictionary alloc]initWithObjects:@[@"1",@"1"] forKeys:@[@"2",@"2"]];
[dict setObject:str forKey:@"3"];
[dict removeObjectForKey:str];
備註:本文用到的部分函數方法和Demo,均來自三方庫KJExtensionHandler,如有需要的朋友可自行pod 'KJExtensionHandler'引入即可

崩潰處理介紹就到此完畢,後面有相關再補充,寫文章不容易,還請點個小星星傳送門

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章