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 method
methodSignatureForSelector:
和forwardInvocation:
- The object calls a method through three phases
- Message sending: query the cache and method list, find the direct call, can not find the method will go to the next stage
- Dynamic resolution: invoke instance methods
resolveInstanceMethod
Or a class methodresolveClassMethod
There can be an opportunity to add methods dynamically - Message forwarding: It first determines whether there are other objects that can handle methods
forwardingTargetForSelector
Returns a new object, called if there are no new objects to processmethodSignatureForSelector
Method 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:
- exchange
removeObserver: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:
- exchange
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
methods - Define an abstract class
KJProxyProtector
NSTimer 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 method
methodSignatureForSelector:
和forwardInvocation:
UIKit Called on non-main Thread
Solution:
- exchange
setNeedsLayout
,layoutIfNeeded
,layoutSubviews
,setNeedsUpdateConstraints
methods [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