AvoidCrash.m 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. //
  2. // AvoidCrash.m
  3. // https://github.com/chenfanfang/AvoidCrash
  4. //
  5. // Created by mac on 16/9/21.
  6. // Copyright © 2016年 chenfanfang. All rights reserved.
  7. //
  8. #import "AvoidCrash.h"
  9. #define key_errorName @"errorName"
  10. #define key_errorReason @"errorReason"
  11. #define key_errorPlace @"errorPlace"
  12. #define key_defaultToDo @"defaultToDo"
  13. #define key_callStackSymbols @"callStackSymbols"
  14. #define key_exception @"exception"
  15. @implementation AvoidCrash
  16. + (void)becomeEffective {
  17. [self effectiveIfDealWithNoneSel:NO];
  18. }
  19. + (void)makeAllEffective {
  20. [self effectiveIfDealWithNoneSel:YES];
  21. }
  22. + (void)effectiveIfDealWithNoneSel:(BOOL)dealWithNoneSel {
  23. static dispatch_once_t onceToken;
  24. dispatch_once(&onceToken, ^{
  25. [NSObject avoidCrashExchangeMethodIfDealWithNoneSel:dealWithNoneSel];
  26. [NSArray avoidCrashExchangeMethod];
  27. [NSMutableArray avoidCrashExchangeMethod];
  28. [NSDictionary avoidCrashExchangeMethod];
  29. [NSMutableDictionary avoidCrashExchangeMethod];
  30. [NSString avoidCrashExchangeMethod];
  31. [NSMutableString avoidCrashExchangeMethod];
  32. [NSAttributedString avoidCrashExchangeMethod];
  33. [NSMutableAttributedString avoidCrashExchangeMethod];
  34. });
  35. }
  36. + (void)setupNoneSelClassStringsArr:(NSArray<NSString *> *)classStrings {
  37. [NSObject setupNoneSelClassStringsArr:classStrings];
  38. }
  39. /**
  40. * 初始化一个需要防止”unrecognized selector sent to instance”的崩溃的类名前缀的数组
  41. */
  42. + (void)setupNoneSelClassStringPrefixsArr:(NSArray<NSString *> *)classStringPrefixs {
  43. [NSObject setupNoneSelClassStringPrefixsArr:classStringPrefixs];
  44. }
  45. /**
  46. * 类方法的交换
  47. *
  48. * @param anClass 哪个类
  49. * @param method1Sel 方法1
  50. * @param method2Sel 方法2
  51. */
  52. + (void)exchangeClassMethod:(Class)anClass method1Sel:(SEL)method1Sel method2Sel:(SEL)method2Sel {
  53. Method method1 = class_getClassMethod(anClass, method1Sel);
  54. Method method2 = class_getClassMethod(anClass, method2Sel);
  55. method_exchangeImplementations(method1, method2);
  56. }
  57. /**
  58. * 对象方法的交换
  59. *
  60. * @param anClass 哪个类
  61. * @param method1Sel 方法1(原本的方法)
  62. * @param method2Sel 方法2(要替换成的方法)
  63. */
  64. + (void)exchangeInstanceMethod:(Class)anClass method1Sel:(SEL)method1Sel method2Sel:(SEL)method2Sel {
  65. Method originalMethod = class_getInstanceMethod(anClass, method1Sel);
  66. Method swizzledMethod = class_getInstanceMethod(anClass, method2Sel);
  67. BOOL didAddMethod =
  68. class_addMethod(anClass,
  69. method1Sel,
  70. method_getImplementation(swizzledMethod),
  71. method_getTypeEncoding(swizzledMethod));
  72. if (didAddMethod) {
  73. class_replaceMethod(anClass,
  74. method2Sel,
  75. method_getImplementation(originalMethod),
  76. method_getTypeEncoding(originalMethod));
  77. }
  78. else {
  79. method_exchangeImplementations(originalMethod, swizzledMethod);
  80. }
  81. }
  82. /**
  83. * 获取堆栈主要崩溃精简化的信息<根据正则表达式匹配出来>
  84. *
  85. * @param callStackSymbols 堆栈主要崩溃信息
  86. *
  87. * @return 堆栈主要崩溃精简化的信息
  88. */
  89. + (NSString *)getMainCallStackSymbolMessageWithCallStackSymbols:(NSArray<NSString *> *)callStackSymbols {
  90. //mainCallStackSymbolMsg的格式为 +[类名 方法名] 或者 -[类名 方法名]
  91. __block NSString *mainCallStackSymbolMsg = nil;
  92. //匹配出来的格式为 +[类名 方法名] 或者 -[类名 方法名]
  93. NSString *regularExpStr = @"[-\\+]\\[.+\\]";
  94. NSRegularExpression *regularExp = [[NSRegularExpression alloc] initWithPattern:regularExpStr options:NSRegularExpressionCaseInsensitive error:nil];
  95. for (int index = 2; index < callStackSymbols.count; index++) {
  96. NSString *callStackSymbol = callStackSymbols[index];
  97. [regularExp enumerateMatchesInString:callStackSymbol options:NSMatchingReportProgress range:NSMakeRange(0, callStackSymbol.length) usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
  98. if (result) {
  99. NSString* tempCallStackSymbolMsg = [callStackSymbol substringWithRange:result.range];
  100. //get className
  101. NSString *className = [tempCallStackSymbolMsg componentsSeparatedByString:@" "].firstObject;
  102. className = [className componentsSeparatedByString:@"["].lastObject;
  103. NSBundle *bundle = [NSBundle bundleForClass:NSClassFromString(className)];
  104. //filter category and system class
  105. if (![className hasSuffix:@")"] && bundle == [NSBundle mainBundle]) {
  106. mainCallStackSymbolMsg = tempCallStackSymbolMsg;
  107. }
  108. *stop = YES;
  109. }
  110. }];
  111. if (mainCallStackSymbolMsg.length) {
  112. break;
  113. }
  114. }
  115. return mainCallStackSymbolMsg;
  116. }
  117. /**
  118. * 提示崩溃的信息(控制台输出、通知)
  119. *
  120. * @param exception 捕获到的异常
  121. * @param defaultToDo 这个框架里默认的做法
  122. */
  123. + (void)noteErrorWithException:(NSException *)exception defaultToDo:(NSString *)defaultToDo {
  124. //堆栈数据
  125. NSArray *callStackSymbolsArr = [NSThread callStackSymbols];
  126. //获取在哪个类的哪个方法中实例化的数组 字符串格式 -[类名 方法名] 或者 +[类名 方法名]
  127. NSString *mainCallStackSymbolMsg = [AvoidCrash getMainCallStackSymbolMessageWithCallStackSymbols:callStackSymbolsArr];
  128. if (mainCallStackSymbolMsg == nil) {
  129. mainCallStackSymbolMsg = @"崩溃方法定位失败,请您查看函数调用栈来排查错误原因";
  130. }
  131. NSString *errorName = exception.name;
  132. NSString *errorReason = exception.reason;
  133. //errorReason 可能为 -[__NSCFConstantString avoidCrashCharacterAtIndex:]: Range or index out of bounds
  134. //将avoidCrash去掉
  135. errorReason = [errorReason stringByReplacingOccurrencesOfString:@"avoidCrash" withString:@""];
  136. NSString *errorPlace = [NSString stringWithFormat:@"Error Place:%@",mainCallStackSymbolMsg];
  137. NSString *logErrorMessage = [NSString stringWithFormat:@"\n\n%@\n\n%@\n%@\n%@\n%@",AvoidCrashSeparatorWithFlag, errorName, errorReason, errorPlace, defaultToDo];
  138. logErrorMessage = [NSString stringWithFormat:@"%@\n\n%@\n\n",logErrorMessage,AvoidCrashSeparator];
  139. AvoidCrashLog(@"%@",logErrorMessage);
  140. //请忽略下面的赋值,目的只是为了能顺利上传到cocoapods
  141. logErrorMessage = logErrorMessage;
  142. NSDictionary *errorInfoDic = @{
  143. key_errorName : errorName,
  144. key_errorReason : errorReason,
  145. key_errorPlace : errorPlace,
  146. key_defaultToDo : defaultToDo,
  147. key_exception : exception,
  148. key_callStackSymbols : callStackSymbolsArr
  149. };
  150. //将错误信息放在字典里,用通知的形式发送出去
  151. dispatch_async(dispatch_get_main_queue(), ^{
  152. [[NSNotificationCenter defaultCenter] postNotificationName:AvoidCrashNotification object:nil userInfo:errorInfoDic];
  153. });
  154. }
  155. @end