• This post was originally posted on my personal blog:”Unruly Pavilion”
  • Article links:portal
  • This article was updated at 12:15:21 on Aug 23, 2019

This article is the first in the “Crash Protection System” series. This series will introduce how to design an APP Crash protection system. This system adopts the design idea of AOP (section-oriented programming), uses the runtime mechanism of Objective-C language, on the basis of not invading the original project code, intercepts and processes the crash factors in the APP runtime stage, so that the APP can run steadily and normally.

Through this article, you will learn:

  1. Crash protection system begins
  2. Introduction to protection principles and common Crash
  3. Method Swizzling Method encapsulation
  4. Unrecognized Selector protection 4.1 Unrecognized Selector sent to instance 4.2 Unrecognized Selector sent to Class (class method implementation not found)

Example code for this article is: bujige/ysc-avoid-crash


1. Crash protection system begins

APP crashes have always been a top priority in the development process. Crashes during the daily development phase can be dealt with immediately after they are discovered. However, once the release on the shelf has a problem, it is urgent to work overtime to fix the BUG, and then update the release of a new version. In the process, a crash may lead to the interruption of key business, the decline of user retention, brand reputation, life cycle value, etc., and ultimately lead to the loss of users, affecting the company’s development.

Of course, the best way to avoid crashes is not to have crashes. In the development process to ensure that the program as robust as possible. But people are not machines, and it is impossible not to make mistakes. There can be no program without bugs. However, if some language mechanisms and systematic methods can be used to design a protective system, which can effectively reduce the crash rate of APP, the stability of APP can be guaranteed, and most importantly, unnecessary overtime can be reduced.

The Crash protection system was named “YSCDefender”. Defender is also land Rover’s toughest off-road vehicle. In the movie Tomb Raider, the Defender is driven by Lara Croft, a British adventurer played by Angelina Jolie. The Defender is also one of my favorites.

But it doesn’t matter… I just gave the project a fancy name and gave it some boring meaning…


2. Introduction to protection principles and common Crash

Objective-c is a dynamic language. We can use the Runtime Runtime mechanism of Objective-C to add a Category (Category) to a class that needs Hook. In Method Swizzling intercepts system methods that are easy to cause crashes, and swaps the selector (Method selector) and IMP (function implementation pointer) of the original system Method and the added protection Method. You then add defensive actions to the replacement method to avoid and repair crashes.

Common crashes that can be avoided using the Runtime mechanism:

  1. Unrecognized selector sent to instance (cannot find implementation of object method)
  2. Unrecognized selector sent to class
  3. KVO Crash
  4. KVC Crash
  5. NSNotification Crash
  6. NSTimer Crash
  7. Container Crash (Crash caused by a collection class operation, e.g. array out of bounds, insert nil, etc.)
  8. NSString Crash (string type operation Crash)
  9. Bad Access Crash (wild pointer)
  10. From the Threading Crash, the user can read the UI from the main thread.
  11. NSNull Crash

Unrecognized selector sent to instance and unrecognized selector sent to class.


3. Method Swizzling 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.

/ * * * * * * * * * * * * * * * * * * * * * NSObject + MethodSwizzling. H file * * * * * * * * * * * * * * * * * * * * * /

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSObject (MethodSwizzling)

/** Swap implementations of two class methods * @param originalSelector SEL of the original method * @param swizzledSelector SEL of the exchange method * @param targetClass class */
+ (void)yscDefenderSwizzlingClassMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector withClass:(Class)targetClass;

/** Swap implementations of two object methods * @param originalSelector SEL of the original method * @param swizzledSelector SEL of the swap method * @param targetClass */
+ (void)yscDefenderSwizzlingInstanceMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector withClass:(Class)targetClass;

@end

/ * * * * * * * * * * * * * * * * * * * * * NSObject + MethodSwizzling. M file * * * * * * * * * * * * * * * * * * * * * /

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

@implementation NSObject (MethodSwizzling)

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

// Swap the implementation of two object methods
+ (void)yscDefenderSwizzlingInstanceMethod:(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

4. Unrecognized Selector defense

4.1 Unrecognized selector sent to instance

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. For example, code like this:

UIButton *testButton = [[UIButton alloc] init];
[testButton performSelector:@selector(someMethod:)];
Copy the code

TestButton is a UIButton object, and there is no someMethod: method implemented in UIButton. Therefore, sending a someMethod: method to a testButoon object will cause the testButoon object to fail to find the corresponding method implementation, resulting in the crash of the APP.

Is there a solution to the problem of crashing because you can’t find a method implementation?

We learned from “iOS development:” Runtime “(1) Basic knowledge” the message forwarding mechanism of the three steps: dynamic message parsing, message receiver redirection, message redirection. These three steps allow us to intercept method calls before the program fails to find the calling method and crashes.

The general process is as follows:

  1. Dynamic message parsing: Called at objective-C runtime+resolveInstanceMethod:or+resolveClassMethod:, gives you the opportunity to provide a function implementation. We can override these two methods, add another function implementation, and return YES, and the runtime system will restart the process of sending a message. If NO is returned or NO other function implementation is added, proceed to the next step.
  2. Message receiver redirection: if the current object is implementedforwardingTargetForSelector:, the Runtime calls this method, allowing us to forward the recipient of the message to other objects. If this step method returnsnil, go to the next step.
  3. Message redirection: Runtime system utilizationmethodSignatureForSelector:Method to get the parameters and return value types of a function.
    • ifmethodSignatureForSelector:Returns aNSMethodSignatureThe Runtime system creates an NSInvocation object and passes itforwardInvocation:The message notifies the current object, giving the message sender one last chance to find the IMP.
    • ifmethodSignatureForSelector:returnnil. The Runtime system emitsdoesNotRecognizeSelector:Message, and the program crashes.

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 steps are as follows:

  1. Add a class to NSObject and implement a custom class within the class-ysc_forwardingTargetForSelector:Methods;
  2. Using Method Swizzling will-forwardingTargetForSelector:-ysc_forwardingTargetForSelector:Do method swaps.
  3. In the custom method, you determine whether the current object has implemented message receiver redirection and message redirection. If none of this is done, create a target class dynamically and add a method to the target class dynamically.
  4. 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 the APP does not crash.

The implementation code is as follows:

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

@implementation NSObject (SelectorDefender)

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

/ / custom implementations ` - ysc_forwardingTargetForSelector: ` method
- (id)ysc_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 redirection
    BOOLrealize = 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 = [NSObject class];
                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 ysc_forwardingTargetForSelector:aSelector];
}

// Dynamically add method implementation
static int Crash(id slf, SEL selector) {
    return 0;
}

@end
Copy the code

4.2 Unrecognized selector sent to class

As with object methods, if the class method being called is not implemented, the APP will crash as well.

For example, there is a class that declares a + (id)aClassFunc; Class method, but not implemented, like YSCObject below.

/ * * * * * * * * * * * * * * * * * * * * * YSCObject. H file * * * * * * * * * * * * * * * * * * * * * /
#import <Foundation/Foundation.h>

@interface YSCObject : NSObject

+ (id)aClassFunc;

@end

/ * * * * * * * * * * * * * * * * * * * * * YSCObject. M file * * * * * * * * * * * * * * * * * * * * * /
#import "YSCObject.h"

@implementation YSCObject

@end
Copy the code

If we just call [YSCObject aClassFunc]; It’s going to crash.

Can’t find the class methods before the implementation of solutions and similar, we can use Method Swizzling will + forwardingTargetForSelector: and + ysc_forwardingTargetForSelector: to exchange Method.

#import "NSObject+SelectorDefender.h" #import "NSObject+MethodSwizzling.h" #import <objc/runtime.h> @implementation NSObject (SelectorDefender) + (void)load { static dispatch_once_t onceToken; Dispatch_once (& onceToken, ^ {/ / intercept ` + forwardingTargetForSelector: ` method, Replace the custom implementation [NSObject yscDefenderSwizzlingClassMethod: @ the selector (forwardingTargetForSelector:) withMethod:@selector(ysc_forwardingTargetForSelector:) withClass:[NSObject class]]; }); } / / custom implementations ` + ysc_forwardingTargetForSelector: + ` method (id) ysc_forwardingTargetForSelector: (SEL) aSelector {SEL forwarding_sel = @selector(forwardingTargetForSelector:); Root_forwarding_method = class_getClassMethod([NSObject class], forwarding_sel); root_forwarding_method = class_getClassMethod([NSObject class], forwarding_sel); Method current_forwarding_method = class_getClassMethod([self class], forwarding_sel); // Determine if the current class itself implements the second step: the message receiver redirects BOOL realize = method_getImplementation(current_forwarding_method)! = method_getImplementation(root_forwarding_method); // If the second step is not implemented: the message receiver redirects if (! Did realize) {/ / judgment to realize the third step: message redirect SEL methodSignature_sel = @ the 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 (! {// create a new class NSString *errClassName = NSStringFromClass([self class]); NSString *errSel = NSStringFromSelector(aSelector); NSLog(@" bad class, bad class method == %@ %@", errClassName, errSel); NSString *className = @"CrachClass"; Class cls = NSClassFromString(className); // dynamically create a class if (! cls) { Class superClsss = [NSObject class]; cls = objc_allocateClassPair(superClsss, className.UTF8String, 0); // Register class objc_registerClassPair(CLS); } // If the class has no corresponding method, then dynamically add an if (! class_getInstanceMethod(NSClassFromString(className), aSelector)) { class_addMethod(cls, aSelector, (IMP)Crash, "@@:@");  Return [[CLS alloc] init]; } } return [self ysc_forwardingTargetForSelector:aSelector]; } static int Crash(id SLF, SEL selector) {return 0; } @endCopy the code

Combining 4.1 and 4.2 allows you to intercept all unimplemented class and object methods. The specific implementation can refer to the code: bujige/ysc-avoid-crash


The resources

  • How Not to Crash
  • IOS prevents apps from crashing
  • Baymax Health system – Crash automatically fixes the system when iOS APP runs

  • Author: Walking boy lang
  • Links to this article: www.jianshu.com/p/aeecc4b46…
  • Copyright Notice: This article is licensed under CC BY-NC-SA 3.0. Please indicate “author of this article” and “link to this article” at the beginning of the text.