220 lines
7.4 KiB
Objective-C
220 lines
7.4 KiB
Objective-C
//
|
||
// AvoidCrash.m
|
||
// https://github.com/chenfanfang/AvoidCrash
|
||
//
|
||
// Created by mac on 16/9/21.
|
||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||
//
|
||
|
||
#import "AvoidCrash.h"
|
||
|
||
|
||
|
||
|
||
#define key_errorName @"errorName"
|
||
#define key_errorReason @"errorReason"
|
||
#define key_errorPlace @"errorPlace"
|
||
#define key_defaultToDo @"defaultToDo"
|
||
#define key_callStackSymbols @"callStackSymbols"
|
||
#define key_exception @"exception"
|
||
|
||
|
||
@implementation AvoidCrash
|
||
|
||
|
||
+ (void)becomeEffective {
|
||
[self effectiveIfDealWithNoneSel:NO];
|
||
|
||
}
|
||
|
||
+ (void)makeAllEffective {
|
||
[self effectiveIfDealWithNoneSel:YES];
|
||
}
|
||
|
||
+ (void)effectiveIfDealWithNoneSel:(BOOL)dealWithNoneSel {
|
||
static dispatch_once_t onceToken;
|
||
dispatch_once(&onceToken, ^{
|
||
|
||
[NSObject avoidCrashExchangeMethodIfDealWithNoneSel:dealWithNoneSel];
|
||
|
||
[NSArray avoidCrashExchangeMethod];
|
||
[NSMutableArray avoidCrashExchangeMethod];
|
||
|
||
[NSDictionary avoidCrashExchangeMethod];
|
||
[NSMutableDictionary avoidCrashExchangeMethod];
|
||
|
||
[NSString avoidCrashExchangeMethod];
|
||
[NSMutableString avoidCrashExchangeMethod];
|
||
|
||
[NSAttributedString avoidCrashExchangeMethod];
|
||
[NSMutableAttributedString avoidCrashExchangeMethod];
|
||
});
|
||
}
|
||
|
||
+ (void)setupNoneSelClassStringsArr:(NSArray<NSString *> *)classStrings {
|
||
[NSObject setupNoneSelClassStringsArr:classStrings];
|
||
}
|
||
|
||
/**
|
||
* 初始化一个需要防止”unrecognized selector sent to instance”的崩溃的类名前缀的数组
|
||
*/
|
||
+ (void)setupNoneSelClassStringPrefixsArr:(NSArray<NSString *> *)classStringPrefixs {
|
||
[NSObject setupNoneSelClassStringPrefixsArr:classStringPrefixs];
|
||
}
|
||
|
||
|
||
/**
|
||
* 类方法的交换
|
||
*
|
||
* @param anClass 哪个类
|
||
* @param method1Sel 方法1
|
||
* @param method2Sel 方法2
|
||
*/
|
||
+ (void)exchangeClassMethod:(Class)anClass method1Sel:(SEL)method1Sel method2Sel:(SEL)method2Sel {
|
||
Method method1 = class_getClassMethod(anClass, method1Sel);
|
||
Method method2 = class_getClassMethod(anClass, method2Sel);
|
||
method_exchangeImplementations(method1, method2);
|
||
}
|
||
|
||
|
||
/**
|
||
* 对象方法的交换
|
||
*
|
||
* @param anClass 哪个类
|
||
* @param method1Sel 方法1(原本的方法)
|
||
* @param method2Sel 方法2(要替换成的方法)
|
||
*/
|
||
+ (void)exchangeInstanceMethod:(Class)anClass method1Sel:(SEL)method1Sel method2Sel:(SEL)method2Sel {
|
||
|
||
|
||
Method originalMethod = class_getInstanceMethod(anClass, method1Sel);
|
||
Method swizzledMethod = class_getInstanceMethod(anClass, method2Sel);
|
||
|
||
BOOL didAddMethod =
|
||
class_addMethod(anClass,
|
||
method1Sel,
|
||
method_getImplementation(swizzledMethod),
|
||
method_getTypeEncoding(swizzledMethod));
|
||
|
||
if (didAddMethod) {
|
||
class_replaceMethod(anClass,
|
||
method2Sel,
|
||
method_getImplementation(originalMethod),
|
||
method_getTypeEncoding(originalMethod));
|
||
}
|
||
|
||
else {
|
||
method_exchangeImplementations(originalMethod, swizzledMethod);
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* 获取堆栈主要崩溃精简化的信息<根据正则表达式匹配出来>
|
||
*
|
||
* @param callStackSymbols 堆栈主要崩溃信息
|
||
*
|
||
* @return 堆栈主要崩溃精简化的信息
|
||
*/
|
||
|
||
+ (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;
|
||
}
|
||
|
||
|
||
/**
|
||
* 提示崩溃的信息(控制台输出、通知)
|
||
*
|
||
* @param exception 捕获到的异常
|
||
* @param defaultToDo 这个框架里默认的做法
|
||
*/
|
||
+ (void)noteErrorWithException:(NSException *)exception defaultToDo:(NSString *)defaultToDo {
|
||
|
||
//堆栈数据
|
||
NSArray *callStackSymbolsArr = [NSThread callStackSymbols];
|
||
|
||
//获取在哪个类的哪个方法中实例化的数组 字符串格式 -[类名 方法名] 或者 +[类名 方法名]
|
||
NSString *mainCallStackSymbolMsg = [AvoidCrash getMainCallStackSymbolMessageWithCallStackSymbols:callStackSymbolsArr];
|
||
|
||
if (mainCallStackSymbolMsg == nil) {
|
||
|
||
mainCallStackSymbolMsg = @"崩溃方法定位失败,请您查看函数调用栈来排查错误原因";
|
||
|
||
}
|
||
|
||
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
|
||
};
|
||
|
||
//将错误信息放在字典里,用通知的形式发送出去
|
||
dispatch_async(dispatch_get_main_queue(), ^{
|
||
[[NSNotificationCenter defaultCenter] postNotificationName:AvoidCrashNotification object:nil userInfo:errorInfoDic];
|
||
});
|
||
}
|
||
|
||
|
||
|
||
@end
|