Application crashes are always the biggest headache. The crash of daily development stage can be dealt with immediately after it is found, but the crash of online needs urgent bug repair and release, which is also a very serious R & D accident. So how to reduce the crash rate of the program? Here, Crash is automatically captured and processed during APP running, enabling the APP to run steadily and normally.

Crash course Crash course Crash course

  • Container Crash(Crash caused by collection class operations, such as array out of bounds, insert nil)
  • NSString Crash(string type operation Crash)
  • Could not find object method or class method
  • KVO and KVC Crash
  • NSNOtification Crash
  • NSTimer Crash
  • Bad Access Crash(wild pointer)
  • From the Threading Crash, the user can read the UI from the main thread.
  • NSNull Crash

Protection principles

Objective-c is a dynamic language. It makes use of the Runtime Runtime mechanism of Objective-C to add categories to classes that need Hook. In the +(void)load of each classification, Method Swizzling is used to intercept system methods that are easy to cause crashes, and the selector (Method selector) and IMP (function implementation pointer) of the original system Method and added protection Method are exchanged. You then add defensive actions to the replacement method to avoid and repair crashes.

Method Swizzling Method encapsulation

Method Swizzling technology is needed to prevent these common crashes. So we can create a new class for NSObject that encapsulates Method Swizzling related methods.

@interface NSObject (Safe)

/** Swap implementations of two class methods@param OriginalSelector SEL of the original method@param SwizzledSelector The SEL of an exchange method@param TargetClass class * /
+ (void)jhDefenderSwizzlingClassMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector withClass:(Class)targetClass;

/** Swap implementations of two object methods@param OriginalSelector SEL of the original method@param SwizzledSelector The SEL of an exchange method@param TargetClass class * /
+ (void)jhDefenderSwizzlingInstanceMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector withClass:(Class)targetClass;

@end
Copy the code
@implementation NSObject (Safe)

// Swap implementations of two class methods
+ (void)iaskDefenderSwizzlingClassMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector withClass:(Class)targetClass {
    swizzlingClassMethod(targetClass, originalSelector, swizzledSelector);
}

// Swap the implementation of two object methods
+ (void)iaskDefenderSwizzlingInstanceMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector withClass:(Class)targetClass  { swizzlingInstanceMethod(targetClass, originalSelector, swizzledSelector); }// Swap the implementation C functions of two class methods
void swizzlingClassMethod(Class class, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getClassMethod(class.originalSelector);
    Method swizzledMethod = class_getClassMethod(class.swizzledSelector);

    BOOL didAddMethod = class_addMethod(class.originalSelector.method_getImplementation(swizzledMethod);                    method_getTypeEncoding(swizzledMethod));

    if (didAddMethod) {
        class_replaceMethod(class.swizzledSelector.method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else{ method_exchangeImplementations(originalMethod, swizzledMethod); }}// Exchange two object methods to implement C functions
void swizzlingInstanceMethod(Class class, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(class.originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class.swizzledSelector);
    BOOL didAddMethod = class_addMethod(class.originalSelector.method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));

    if (didAddMethod) {
        class_replaceMethod(class.swizzledSelector.method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

@end
Copy the code

Here’s how to implement the code to prevent crashes.

Common Crash and protection measures

1. Container Crash

NSArray/NSMutableArray/NSDictionary/NSMutableDictionary/NSCache crash Some common error operations such as crossing boundaries and inserting nil will lead to such crashes.

Solution: For methods prone to crash, custom methods are exchanged, and some conditions and judgments are added to the custom methods.

NSArray+Safe

Create NSArray class (NSArray+Safe).

#import "NSObject+Swizzling.h"
#import <objc/runtime.h>

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        / / replace objectAtIndex
        NSString *tmpStr = @"objectAtIndex:";
        NSString *tmpFirstStr = @"safe_ZeroObjectAtIndex:";
        NSString *tmpThreeStr = @"safe_objectAtIndex:";
        NSString *tmpSecondStr = @"safe_singleObjectAtIndex:";

        / / replace objectAtIndexedSubscript
        NSString *tmpSubscriptStr = @"objectAtIndexedSubscript:";
        NSString *tmpSecondSubscriptStr = @"safe_objectAtIndexedSubscript:";

        [NSObject exchangeInstanceMethodWithSelfClass:NSClassFromString(@"__NSArray0")
                                     originalSelector:NSSelectorFromString(tmpStr)                                     swizzledSelector:NSSelectorFromString(tmpFirstStr)];

        [NSObject exchangeInstanceMethodWithSelfClass:NSClassFromString(@"__NSSingleObjectArrayI")
                                     originalSelector:NSSelectorFromString(tmpStr)                                     swizzledSelector:NSSelectorFromString(tmpSecondStr)];
                                     
        [NSObject exchangeInstanceMethodWithSelfClass:NSClassFromString(@"__NSArrayI")
                                     originalSelector:NSSelectorFromString(tmpStr)                                     swizzledSelector:NSSelectorFromString(tmpThreeStr)];
      
        [NSObject exchangeInstanceMethodWithSelfClass:NSClassFromString(@"__NSArrayI")               originalSelector:NSSelectorFromString(tmpSubscriptStr)                                     swizzledSelector:NSSelectorFromString(tmpSecondSubscriptStr)];
    });
}

#pragma mark --- implement method

/** The index value of NSArray is __NSArrayI@param The index index index@return The return value * /

- (id)safe_objectAtIndex:(NSUInteger)index {
    if (index >= self.count){
        return nil;
    }
    return [self safe_objectAtIndex:index];
}

/** Select __NSSingleObjectArrayI from NSArray@param The index index index@return The return value * /
- (id)safe_singleObjectAtIndex:(NSUInteger)index {
    if (index >= self.count){
        return nil;
    }
    return [self safe_singleObjectAtIndex:index];
}

/** The index value of NSArray is __NSArray0@param The index index index@return The return value * /
- (id)safe_ZeroObjectAtIndex:(NSUInteger)index {
    if (index >= self.count){
        return nil;
    }
    return [self safe_ZeroObjectAtIndex:index];
}

/** The index value of NSArray is __NSArrayI@param Independence idx index independence idx@return The return value * /
- (id)safe_objectAtIndexedSubscript:(NSUInteger)idx {
    if (idx >= self.count){
        return nil;
    }
    return [self safe_objectAtIndexedSubscript:idx];
}
Copy the code

NSMutableArray+Safe

#import "NSObject+Swizzling.h"
#import <objc/runtime.h>
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        / / replace objectAtIndex:
        NSString *tmpGetStr = @"objectAtIndex:";
        NSString *tmpSafeGetStr = @"safeMutable_objectAtIndex:";
        [NSObject exchangeInstanceMethodWithSelfClass:NSClassFromString(@"__NSArrayM")
                                     originalSelector:NSSelectorFromString(tmpGetStr)                                    swizzledSelector:NSSelectorFromString(tmpSafeGetStr)];

        / / replace removeObjectsInRange:
        NSString *tmpRemoveStr = @"removeObjectsInRange:";
        NSString *tmpSafeRemoveStr = @"safeMutable_removeObjectsInRange:";
        [NSObject exchangeInstanceMethodWithSelfClass:NSClassFromString(@"__NSArrayM")
                         originalSelector:NSSelectorFromString(tmpRemoveStr)                                     swizzledSelector:NSSelectorFromString(tmpSafeRemoveStr)];

        / / replace insertObject: atIndex:
        NSString *tmpInsertStr = @"insertObject:atIndex:";
        NSString *tmpSafeInsertStr = @"safeMutable_insertObject:atIndex:";
        [NSObject exchangeInstanceMethodWithSelfClass:NSClassFromString(@"__NSArrayM")
                          originalSelector:NSSelectorFromString(tmpInsertStr)                                     swizzledSelector:NSSelectorFromString(tmpSafeInsertStr)];

        / / replace removeObject: inRange:
        NSString *tmpRemoveRangeStr = @"removeObject:inRange:";
        NSString *tmpSafeRemoveRangeStr = @"safeMutable_removeObject:inRange:";
        [NSObject exchangeInstanceMethodWithSelfClass:NSClassFromString(@"__NSArrayM")
                           originalSelector:NSSelectorFromString(tmpRemoveRangeStr)                             swizzledSelector:NSSelectorFromString(tmpSafeRemoveRangeStr)];

        / / replace objectAtIndexedSubscript
        NSString *tmpSubscriptStr = @"objectAtIndexedSubscript:";
        NSString *tmpSecondSubscriptStr = @"safeMutable_objectAtIndexedSubscript:";
        [NSObject exchangeInstanceMethodWithSelfClass:NSClassFromString(@"__NSArrayM")
                      originalSelector:NSSelectorFromString(tmpSubscriptStr)                                    swizzledSelector:NSSelectorFromString(tmpSecondSubscriptStr)];
    });
}

#pragma mark --- implement method

/** Fetch the index value of NSArray@param The index index index@return The return value * /
- (id)safeMutable_objectAtIndex:(NSUInteger)index {

    if (index >= self.count){
        return nil;
    }
    return [self safeMutable_objectAtIndex:index];
}

/** NSMutableArray removes the value corresponding to index index@param Range Removes the range */
- (void)safeMutable_removeObjectsInRange:(NSRange)range {
    if ((range.location + range.length) > self.count) {
        return;
    }

    return [self safeMutable_removeObjectsInRange:range];
}

/** Remove the anObject within the range@param AnObject Indicates the anObject to be removed@param * / the scope of the range

- (void)safeMutable_removeObject:(id)anObject inRange:(NSRange)range {
 
    if ((range.location + range.length) > self.count) {
        return;
    }

    if(! anObject){return;
    }

    return [self safeMutable_removeObject:anObject inRange:range];
}

/** NSMutableArray inserts a new value into the index at the specified position@param AnObject new value@param Index Index index */
- (void)safeMutable_insertObject:(id)anObject atIndex:(NSUInteger)index {
    if (index > self.count) {
        return;
    }

    if(! anObject){return;
    }

    [self safeMutable_insertObject:anObject atIndex:index];
}

/** The index value of NSArray is __NSArrayI@param Independence idx index independence idx@return The return value * /

- (id)safeMutable_objectAtIndexedSubscript:(NSUInteger)idx {
    if (idx >= self.count){
        return nil;
    }

    return [self safeMutable_objectAtIndexedSubscript:idx];
}
Copy the code

Here I’m just writing out the code implementation of NSArray and NSMutableArray, and in fact NSDictionary and NSString are also implemented in the same way, swapping system methods, handling exceptions in methods.

2. Unrecognized Selector

If the called object method is not implemented, the APP will crash because the corresponding method implementation cannot be found when the program calls the method at run time. Then you can intercept method calls before you can find another method crash.

Message forwarding process:

  • Dynamic method resolution: when an object or class receives an undecipherable message, it first calls +resolveInstanceMethod: or +resolveClassMethod: to indicate whether a method implementation will be added to the class. If no implementation is added, go to the next step.

  • Redundant recipient (receiver) : if the current object implementation forwardingTargetForSelector:, the Runtime will call this method, allow us to forward the recipient of the message to other objects. If nil is returned, the next forwarding process is entered.

  • The complete message forwarding: if methodSignatureForSelector: return to nil, will can’t find method error. If the method returns a function signature, an NSInvocation object is created to encapsulates the details of the unprocessed message, changing the invocation target so that the message is invoked on the new target.

Here we choose the second step (message receiver redirection) for interception. Because – forwardingTargetForSelector method can forward the message to an object, spending less, and be rewritten probability is low, suitable for rewriting.

The specific implementation steps are as follows:

  • Add a category to NSObject, implement a custom – in a classification jh_forwardingTargetForSelector: method;

  • Using the Method Swizzling will – forwardingTargetForSelector: and – jh_forwardingTargetForSelector: exchange Method;

  • In the custom method, you determine whether the current object has implemented message receiver redirection and message redirection. If none of this happens, create a target class dynamically and add a method to the target class dynamically.

  • The message is forwarded to the instance object of the dynamically generated class, implemented by the method dynamically created by the target class, so that there is no missing method;

The implementation code is as follows:

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        / / intercept ` - forwardingTargetForSelector: ` method, replacing the custom implementation
        [NSObject iaskDefenderSwizzlingInstanceMethod: @selector(forwardingTargetForSelector:)withMethod: @selector(jh_forwardingTargetForSelector:)withClass:[NSObject class]];

        [NSObject iaskDefenderSwizzlingClassMethod: @selector(forwardingTargetForSelector:)withMethod: @selector(jh_forwardingTargetForSelector:) withClass:[NSObject class]];
    });
}

// Instance method
- (id)jh_forwardingTargetForSelector:(SEL)aSelector {

    SEL forwarding_sel = @selector(forwardingTargetForSelector:);
    
    // Gets the message forwarding method of NSObject
    Method root_forwarding_method = class_getInstanceMethod([NSObject class].forwarding_sel);
    
    // Gets the message forwarding method of the current class
    Method current_forwarding_method = class_getInstanceMethod([self class].forwarding_sel);

    // Determine whether the current class itself implements the second step: message receiver redirectionBOOL realize = method_getImplementation(current_forwarding_method) ! = method_getImplementation(root_forwarding_method);// If the second step is not implemented: message receiver redirection
    if(! realize) {// Determine if the third step, message redirection, is implemented
        SEL methodSignature_sel = @selector(methodSignatureForSelector:);
        Method root_methodSignature_method = class_getInstanceMethod([NSObject class].methodSignature_sel);

        Method current_methodSignature_method = class_getInstanceMethod([self class].methodSignature_sel); realize = method_getImplementation(current_methodSignature_method) ! = method_getImplementation(root_methodSignature_method);// If the third step is not implemented: message redirection
        if(! realize) {// Create a new class
            NSString *errClassName = NSStringFromClass([self class]);
            NSString *errSel = NSStringFromSelector(aSelector);
            NSLog(@"Problem class, problem object method == %@ %@", errClassName, errSel);

            NSString *className = @"CrachClass";
            Class cls = NSClassFromString(className);
            
            // Create a class dynamically if the class does not exist
            if(! cls) { Class superClsss = [NSObjectclass];
                cls = objc_allocateClassPair(superClsss, className.UTF8String, 0);
                / / registered classes
                objc_registerClassPair(cls);
            }
            // If the class has no corresponding method, add one dynamically
            if(! class_getInstanceMethod(NSClassFromString(className), aSelector)) { class_addMethod(cls, aSelector, (IMP)Crash,"@ @ : @");
            }

            // Forward the message to the instance object of the currently dynamically generated class
            return[[cls alloc] init]; }}return [self jh_forwardingTargetForSelector:aSelector];
}

/ / class methods
+ (id)jh_forwardingTargetForSelector:(SEL)aSelector {

    SEL forwarding_sel = @selector(forwardingTargetForSelector:);

    // Gets the message forwarding method of NSObject
    Method root_forwarding_method = class_getClassMethod([NSObject class].forwarding_sel);

    // Gets the message forwarding method of the current class
    Method current_forwarding_method = class_getClassMethod([self class].forwarding_sel);

    // Determine whether the current class itself implements the second step: message receiver redirectionBOOL realize = method_getImplementation(current_forwarding_method) ! = method_getImplementation(root_forwarding_method);// If the second step is not implemented: message receiver redirection
    if(! realize) {// Determine if the third step, message redirection, is implemented
        SEL methodSignature_sel = @selector(methodSignatureForSelector:);

        Method root_methodSignature_method = class_getClassMethod([NSObject class].methodSignature_sel);
        
        Method current_methodSignature_method = class_getClassMethod([self class].methodSignature_sel); realize = method_getImplementation(current_methodSignature_method) ! = method_getImplementation(root_methodSignature_method);// If the third step is not implemented: message redirection
        if(! realize) {// Create a new class
            NSString *errClassName = NSStringFromClass([self class]);
            NSString *errSel = NSStringFromSelector(aSelector);
            NSLog(@"Problem class, problem class method == %@ %@", errClassName, errSel);
            NSString *className = @"CrachClass";
            Class cls = NSClassFromString(className);

            // Create a class dynamically if the class does not exist
            if(! cls) { Class superClsss = [NSObjectclass];
                cls = objc_allocateClassPair(superClsss, className.UTF8String, 0);
                / / registered classes
                objc_registerClassPair(cls);
            }
            // If the class has no corresponding method, add one dynamically
            if(! class_getInstanceMethod(NSClassFromString(className), aSelector)) { class_addMethod(cls, aSelector, (IMP)Crash,"@ @ : @");
            }
            // Forward the message to the instance object of the currently dynamically generated class
            return[[cls alloc] init]; }}return [self jh_forwardingTargetForSelector:aSelector];
}

// Dynamically add method implementation

static int Crash(id slf, SEL selector) {
    return 0;
}
Copy the code

3. KVC Crash

What is KVC?

KVC stands for key-value encoding and provides a mechanism for indirectly accessing the properties of an object.

Common causes of KVC crashes:

  • Key is not an object property, causing a crash.
  • [Fixed] keyPath not correct, causing crash
  • If key is nil, it crashes;
  • If value is nil, it sets a value for a non-object, causing a crash.

Setter search pattern for KVC

KVC Getter search mode

As can be seen from the above flow, setValue:forKey: execution failure calls setValue: forUndefinedKey: and causes a crash; ValueForKey: the valueForUndefinedKey: method is called on failure and a crash occurs.

Corresponding solutions are given for the above crashes:

  • rewritesetValue: forUndefinedKey:Methods andvalueForUndefinedKey:Methods;
  • The problem with a key of nil, just swap the system’ssetValue:forKey:Methods;
  • If value is nil, you need to rewrite the systemsetNilValueForKey:methods

4. KVO Crash

What is KVO?

KVO is key-value pair observation, an implementation of the iOS Observer mode. KVO allows one object to listen for changes to specific properties of another object and receive events when they change.

Common causes of KVO crashes:

  • KVO adding times and removing times do not match;
  • The subject is released early, and the subject is still registered with KVO at dealloc.
  • Add or remove keypath == nil;
  • Add the observer, but the unrealized observeValueForKeyPath: ofObject: change: context: method

Solution:

  • Create a KVOProxy object using {keypath: observer1, observer2… } structure of the relational hash table for observer, keyPath between maintenance;
  • Then KVOProxy object is used to distribute the add, remove and observe methods.
  • The dealloc implementation is customized in the classification to remove redundant observers.

5. NSNotification Crash

Cause: After notification is added to an object, if the dealloc still holds notification, an NSNotification crash will occur. A crash of the NSNotification type occurs when a programmer inadvertently writes code, forgetting to remove the object dealloc after adding an object as an observer to the NSNotificationCenter.

IOS9 before crash, iOS9 after the Apple system has been optimized. Notification crash is no longer possible after iOS9, even if developers do not remove the observer.

[[NSNotificationCenter defaultCenter] removeObserver:self] [NSNotificationCenter defaultCenter] removeObserver:self]

6. NSTimer Crash

Possible cause: The target cannot be released because the timer strongly references the target, causing memory leakage. At the same time, if NSTimer performs a task infinitely repeatedly, it may cause target’s selector to be repeatedly called and in invalid state, which is an unnecessary waste of CPU, memory and other performance aspects of app.

Solution: Define an abstract class in which NSTimer instances strongly reference the abstract class and in which target is weakly referenced. The relationship between target and NSTimer is weakly referenced, meaning that target can be freed freely, thus solving the problem of circular references.

7. Bad Access Crash

A wild pointer is a pointer to a deleted object or a restricted memory area. It is very common for the pointer to point to memory that has been reclaimed somewhere else, but the pointer does not know that it still points to that memory.

Solving a crash caused by wild Pointers is often a tricky thing, because the scene that caused the crash is not easy to reproduce, and the console information provides limited help after another crash. XCode itself, in order to facilitate the discovery of wild Pointers during open debugging, provides Zombie mechanism, which can prompt the occurrence of wild Pointers in the class, thus solving the problem of wild Pointers in the development stage. However, there is still no good way to locate the wild pointer problem on the line.

Wild pointer crash protection scheme:

XCode provides a Zombie mechanism to check for wild Pointers, so we can implement a Zombie mechanism, with all the method blocking mechanism and message forwarding mechanism for Zombie instances. Then it can be done in the wild pointer access does not Crash, but only Crash related information.

It’s also important to note that the zombie mechanism requires objects to keep their Pointers and memory footprint when they are released. As the app progresses, more and more objects are created and released, resulting in a larger memory footprint, which obviously has an impact on the performance of a functioning app. A zombie object release mechanism is needed to ensure that the impact of the zombie mechanism on memory is limited.

Zombie implementation is divided into four parts:

The first step: Method swizzling replaces NSObject’s allocWithZone method. In the new method, determine whether an object of this type needs to be protected against wild Pointers. If so, set a flag for the object with objc_setAssociatedObject. The tagged object then goes through the zombie process

Flag because many system classes, such as NSString and UIView, are created and released very frequently, and the probability of wild Pointers in these instances is very low. Basically, it is the classes we write that have the problem of wild Pointers. Therefore, we can improve the efficiency of the scheme by setting a flag at the time of creation to filter instances that do not need to be protected by wild Pointers.

In addition, we added the blacklist mechanism, because certain classes are not suitable to be added to the zombie mechanism and crash (for example: NSBundle), and all classes related to the zombie mechanism cannot be tagged, as they will loop references and calls during the release process, causing memory leaks and even stack overflows.

Step 2: Method Swizzling replaces NSObject’s dealloc method, calls objc_destructInstance on the flag object instance, releases the related attributes referenced by the instance, and then changes the instance’s ISA to HTZombieObject. Save the original class name in the instance with objc_setAssociatedObject.

Objc_destructInstance is called for:

Object C Runtime NSZombies. Dealloc calls ObjectDispose, which does three things.

  • Call objc_destructInstance to release related instances referenced by this instance
  • Change the ISA of this instance to stubClass to accept arbitrary method calls
  • Free the memory

Objc_destructInstance is explained in the official documentation:

Destroys an instance of a class without freeing memory and removes any associated references this instance might have had.
Copy the code

Objc_destructInstance frees references associated with the instance, but does not free memory such as the instance.

Step 3: In HTZombieObject via message forwarding mechanism forwardingTargetForSelector handle all intercept method, according to the selector can dynamically add processing method of responder HTStubObject instance, Then use objc_getAssociatedObject to get the original class name of the instance that was saved before, and count the error data.

The processing of HTZombieObject is the same as that of unrecognized selector crash. The main purpose is to intercept all functions passed to HTZombieObject and replace them with a function that returns nothing, so that the program does not crash.

Step 4: When you retire to the background or reach the upper limit of unfreed instances, call the original dealloc method in the ht_freeSomeMemory method to release all zombie instances

To sum up, the prevention process of bad Access crash can be summarized as follows:

Due to delayed release of several instances, the total memory of the system will be affected to some extent. Currently, the buffer of the memory is opened to about 2M, so there should be no great impact, but there may be some potential risks.

The delayed release of the instance is based on the assumption that the relevant function code will be called in a certain period of time, so the zombie protection mechanism of the wild pointer can only be effective when the actual instance object is still cached in the zombie cache mechanism. If the instance is actually released, the call of the wild pointer will still cause Crash.

8. Non-mainline UI brush (Crash)

Why only refresh the UI on the main thread?

UIKit is not a thread-safe class. UI operations are designed to access properties of various View objects until rendered. If asynchronous operations have read and write problems, locking them can be costly and slow down. On the other hand, since UIApplication is initialized on the main thread, all user events are delivered on the main thread, so the View can only respond to events on the main thread. In terms of rendering, the image needs to be updated simultaneously on the screen at a refresh rate of 60 frames. In the case of non-main-thread asynchronous calls, it is not certain that this process can achieve synchronous updates.

The current preliminary processing scheme is the following three methods of swizzle UIView class:

-(void)setNeedsLayout;

-(void)setNeedsDisplay;

-(void)setNeedsDisplayInRect:(CGRect)rect;

Dispatch_async (dispatch_get_main_queue(), ^{// call the original method}); However, after the implementation, it is found that these three methods can not completely cover all the brush UI to UIView-related operations. So far I have found this way to solve the problem.

9. NSNull Crash

When parsing backend Json data, sometimes NSNull is returned for no reason (the implementation is not allowed to return NUll), causing the App to blink. We know that we cannot send messages to the NSNull type.

The first method: AFN comes with a property that removes a key with a value of NULL from the response JSON

AFJSONResponseSerializer *responseSerializer = [AFJSONResponseSerializer serializer];
responseSerializer.removesKeysWithNullValues = YES;
Copy the code

NullSafe analysis implementation: The OC runtime mechanism is still used here, and the key is message forwarding. Create a Null category NullSafe and override the dynamic forwarding method. The method signature is searched first. If it can not be obtained, the commonly used Foundation framework class is traversed and verified. If it can respond to this method, a new method signature is generated, and then the next step is forwarded. The last step is to forward the method and set the target object to nil. Sending messages like nil will not crash.

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
    //look up method signature
    NSMethodSignature *signature = [super methodSignatureForSelector:selector];
    if(! signature) {for (Class someClass in @[
            [NSMutableArray class],
            [NSMutableDictionary class],
            [NSMutableString class],
            [NSNumber class],
            [NSDate class],
            [NSData class]]){@try
            {
                if ([someClass instancesRespondToSelector:selector])
                {
                    signature = [someClass instanceMethodSignatureForSelector:selector];
                    break; }} @catch (__unused NSException *unused) {}
        }
    }

    return signature;
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
    invocation.target = nil;
    [invocation invoke];
}
Copy the code

Conclusion:

In the project, we can define a class to manage the Crash protection, whether to enable it or not. Because in our development process, we still want to be able to find problems in time, and then fix problems in time.

We also need to properly balance the types of protection that can be enabled. By default, only the common types of online feedback can be enabled, rather than all types. Other types can be dynamically enabled, and protection can be enabled based on the flash back logs of user devices. Secondly, all kinds of hooks bring uncertainty. Crash itself is only produced under abnormal circumstances. If such exceptions are avoided blindly, more abnormal situations may be generated, especially the uncontrollable flow of business logic. So we usually also want to pay attention to the quality of the code, strictly prevent the occurrence of those low-level errors, should be to avoid errors, rather than to protect the occurrence of errors.

Reference article:

Juejin. Cn/post / 684490…

Baymax Health System

Github.com/jezzmemo/JJ…