本文主要介绍如何防止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];