Files
2025-08-11 10:43:19 +08:00

220 lines
7.4 KiB
Objective-C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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