preface

  • Crash can be divided into two types. One is caused by EXC_BAD_ACCESS. The reason is that the memory address that does not belong to the process is accessed. The other is an uncaught Objective-C exception (NSException) that causes the program to crash by sending a SIGABRT signal to itself
  • This paper mainly introduces the second automatic Crash protection function during APP running, including container, string, failure to find the corresponding function, Crash caused by NSNull returned in the background, timer, KVO, failure to update UI in the main thread and other crashes
  • Fill in other types when you have time

The Demo address:KJExceptionDemo

Design principle

Using the dynamic characteristics of Objective-C language, the design idea of AOP oriented aspect programming is adopted to exchange methods and then intercept and process crash information

methods function
class_getInstanceMethod Get instance method
class_getClassMethod Get class methods
method_getImplementation Gets the implementation of a method
method_setImplementation Sets the implementation of a method
method_getTypeEncoding Gets the encoding type implemented by the method
class_addMethod Add method implementation
class_replaceMethod Replace the implementation of one method with the implementation of another method, that is, aIMP points to bIMP, but bIMP does not necessarily point to aIMP
method_exchangeImplementations Swap the implementations of the two methods, aIMP -> bIMP, bIMP -> aIMP

Exchange instance method

void kExceptionMethodSwizzling(Class clazz, SEL original, SEL swizzled){ Method originalMethod = class_getInstanceMethod(clazz, original); Method swizzledMethod = class_getInstanceMethod(clazz, swizzled); if (class_addMethod(clazz, original, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) { class_replaceMethod(clazz, swizzled, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); }else{ method_exchangeImplementations(originalMethod, swizzledMethod); }}Copy the code

Exchange class method

void kExceptionClassMethodSwizzling(Class clazz, SEL original, SEL swizzled){ Method originalMethod = class_getClassMethod(clazz, original); Method swizzledMethod = class_getClassMethod(clazz, swizzled); Class metaclass = objc_getMetaClass(NSStringFromClass(clazz).UTF8String); if (class_addMethod(metaclass, original, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) { class_replaceMethod(metaclass, swizzled, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); }else{ method_exchangeImplementations(originalMethod, swizzledMethod); }}Copy the code

Common Crash and solutions

Unrecognized Selector Sent to Instance

type why
SEL Unrecognized selector sent to instance. h defined but.m not implemented
SEL PerformSelector: Calls a method that doesn’t exist
SEL Instead of nullating before the delegate callback, call directly
SEL The id type does not determine the type, forcing a method that does not exist for the real type
SEL Copy modifies mutable string dictionary array collection Data to call mutable methods

Message forwarding flowchart:

Solution:

  • Exchange methodmethodSignatureForSelector:forwardInvocation:
  • The object calls a method through three phases
    1. Message sending: query the cache and method list, find the direct call, can not find the method will go to the next stage
    2. Dynamic resolution: invoke instance methodsresolveInstanceMethodOr a class methodresolveClassMethodThere can be an opportunity to add methods dynamically
    3. Message forwarding: It first determines whether there are other objects that can handle methodsforwardingTargetForSelectorReturns a new object, called if there are no new objects to processmethodSignatureForSelectorMethod returns the method signature and then callsforwardInvocation
  • Choose to do the processing at the end of the message forwarding,methodSignatureForSelector:The message gets the parameters and return values of the function, and then[self respondsToSelector:aSelector]Determine if the method exists, and if no function signature is returned, create an NSInvocation object and send itforwardInvocation

Container out of bounds – arrays and dictionaries

type why
NSArray Array index out of bounds, insert empty object
NSDictionary The key and value are empty

Note: mutable is inherited from immutable, and in all mutable categories, repeated methods need not be replaced again

Solution:

  • Swap methods, and then guard, for example, NSArray is a class cluster, and its real type is__NSArrayI, the exchange method is as follows
Class __NSArrayI = objc_getClass("__NSArrayI"); [array objectAtIndex:0]; kExceptionMethodSwizzling(__NSArrayI, @selector(objectAtIndex:), @selector(kj_objectAtIndex:)); Array [0]; kExceptionMethodSwizzling(__NSArrayI, @selector(objectAtIndexedSubscript:), @selector(kj_objectAtIndexedSubscript:));Copy the code

Post-exchange processing

- (instancetype)kj_objectAtIndex:(NSUInteger)index{ NSArray *temp = nil; @try { temp = [self kj_objectAtIndex:index]; }@catch (NSException *exception) {NSString *string = @"🍉🍉 crash: "; If (self.count == 0) {string = [string stringByAppendingString:@" array number = 0 "]; }else if (self.count <= index) {string = [string stringByAppendingString:@" arrayindex "]; } [KJCrashManager kj_crashDealWithException:exception CrashTitle:string]; }@finally { return temp; }}Copy the code

Third, KVO

type why
KVO Listener added, not removed

Solution:

  • exchangeremoveObserver:forKeyPath:Method,
- (void)kj_removeObserver:(NSObject*)observer forKeyPath:(NSString *)keyPath{ @try { [self kj_removeObserver:observer forKeyPath:keyPath]; }@catch (NSException *exception) {NSString *string = @"🍉🍉 crash: failed to remove observer "; [KJCrashManager kj_crashDealWithException:exception CrashTitle:string]; }@finally { } }Copy the code

Fourth, the NSTimer

type why
NSTimer Destroy without invalidate
NSTimer NStimer and target are strongly referenced, memory leaks

Solution:

  • exchangescheduledTimerWithTimeInterval:target:selector:userInfo:repeats:methods
  • Define an abstract classKJProxyProtectorNSTimer instances have strong references to abstract classes and weak references to Target in abstract classes. The relationship between Target and NSTimer is weak references, which means that target can be released freely to solve the problem of circular references

Crash caused by NSNull

type why
NSNull The background returns a crash caused by NSNull
#### Solution:
  • Exchange methodmethodSignatureForSelector:forwardInvocation:

UIKit Called on non-main Thread

Solution:

  • exchangesetNeedsLayout,layoutIfNeeded,layoutSubviews,setNeedsUpdateConstraintsmethods
  • [NSThread isMainThread]Determines whether the current thread is the master thread, and if not, executes on the main thread

Abnormal collection

First, protection type

The following seven are currently available

typedef NS_OPTIONS(NSInteger, KJCrashProtectorType) {KJCrashProtectorTypeContainer = 1 < < 0, / / arrays and dictionaries KJCrashProtectorTypeString = 1 < < 1, / / a string KJCrashProtectorTypeUnrecognizedSelector = 1 < < 2, / / didn't find the corresponding function KJCrashProtectorTypeNSNull = 1 < < return NSNull 3, / / the background lead to collapse KJCrashProtectorTypeTimer = 1 < < 4, / / timer KJCrashProtectorTypeKVO = 1 < < 5, / / kvo KJCrashProtectorTypeUINonMain = 1 < < 6,// do not refresh UI in main thread};Copy the code

Two, open protection

Multiple enumerations are used to quickly set up the protection to be developed

/ / / open all protection + (void) kj_openAllCrashProtectorManager: (kExceptionBlock) block {if (block) [KJCrashManager kj_crashBlock:block]; [self kj_openCrashProtectorType: KJCrashProtectorTypeContainer | KJCrashProtectorTypeString | KJCrashProtectorTypeUnrecognizedSelector | KJCrashProtectorTypeNSNull | KJCrashProtectorTypeTimer | KJCrashProtectorTypeKVO]; } / / / open the specified type protection + (void) kj_openCrashProtectorType: (KJCrashProtectorType) type {if (type & KJCrashProtectorTypeContainer) {  [NSArray kj_openCrashExchangeMethod]; [NSMutableArray kj_openCrashExchangeMethod]; [NSDictionary kj_openCrashExchangeMethod]; [NSMutableDictionary kj_openCrashExchangeMethod]; } if (type & KJCrashProtectorTypeString) { [NSString kj_openCrashExchangeMethod]; [NSMutableString kj_openCrashExchangeMethod]; [NSAttributedString kj_openCrashExchangeMethod]; [NSMutableAttributedString kj_openCrashExchangeMethod]; } if (type & KJCrashProtectorTypeUnrecognizedSelector) { [NSObject kj_openUnrecognizedSelectorExchangeMethod]; } if (type & KJCrashProtectorTypeNSNull) { [NSNull kj_openNullExchangeMethod]; } if (type & KJCrashProtectorTypeTimer) { [NSTimer kj_openCrashExchangeMethod]; } if (type & KJCrashProtectorTypeKVO) { [NSObject kj_openKVOExchangeMethod]; } if (type & KJCrashProtectorTypeUINonMain) { [UIView kj_openCrashExchangeMethod]; }}Copy the code

Parse exception messages

Use regular expressions to match method names

/ / / parse the exception message + (nsstrings *) kj_analysisCallStackSymbols: (NSArray < > nsstrings * *) callStackSymbols {__block nsstrings * 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; }Copy the code

A familiar and hated crash

*** 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 NSExceptionCopy the code

The effect of anti-collapse treatment

The 2020-12-29 15:49:27. 649011 + 0800 KJExceptionDemo [7987, 427289] * * * * * * * * * * * * crash log * * * * * * * * * * * * title: 🍉 🍉 crash: Test_UnrecognizedSelector 🚗🚗 class method not found 🚗🚗 exception address: -[ViewController testUnrecognizedSelector] 2020-12-29 15:49:27.651701+0800 KJExceptionDemo[7987:427289] ************ Crash log ************ title: 🍉🍉 Crash: ViewController class failed to find instance method error cause: testCrash:xx: 🚗🚗 instance method not found 🚗🚗 abnormal address: -[ViewController testUnrecognizedSelector] 2020-12-29 15:49:27.654808+0800 KJExceptionDemo[7987:427289] ************ Crash log ************ title: 🍉🍉 crash: Array insert data is empty Cause: *** -[__NSArrayM insertObject:atIndex:]: Object cannot be nil -[ViewController testContainer] 2020-12-29 15:49:27.657423+0800 KJExceptionDemo[7987:427289] ************ Crash log * * * * * * * * * * * * title: 🍉 🍉 crash: array index change cross-border abnormal reasons: * * * - [__NSArrayM setObject: atIndexedSubscript:] : Index 4 beyond bounds [0.. 2] -[ViewController testContainer] 2020-12-29 15:49:27.661423+0800 KJExceptionDemo[7987:427289] ************ Crash log ************ title: 🍉🍉 crash: String length not enough cause: *** -[__NSCFConstantString kj_substringFromIndex:]: Index 10 out of bounds; String length 3 Abnormal address: -[ViewController testString]Copy the code

Cocoapods installation

pod 'KJExceptionDemo'
Copy the code

Crash processing introduction is over here, there are related to supplement, it is not easy to write an article, but also please point a **Little stars* * portal