目标:在App运行时发生Crash,自动进行保护或修复,让App流畅运行,不至于闪退退出;
iOS常见的Crash包括:

  1. 字典Crash
  2. 数组Crash
  3. 字符串异常

解决方案:

void swizzleMethodWithMetaClass(Class cls, SEL originSelector, Method oriMethod, SEL swizzleSelector, Method swizzledMethod);

void swizzleClassMethod(Class cls, SEL originSelector, SEL swizzleSelector){
    if (!cls) {
        return;
    }
    Method originalMethod = class_getClassMethod(cls, originSelector);
    Method swizzledMethod = class_getClassMethod(cls, swizzleSelector);
    
    Class metacls = objc_getMetaClass(NSStringFromClass(cls).UTF8String);
    swizzleMethodWithMetaClass(metacls, originSelector, originalMethod, swizzleSelector, swizzledMethod);
}

void swizzleInstanceMethod(Class cls, SEL originSelector, SEL swizzleSelector){
    if (!cls) {
        return;
    }
    
    Method originalMethod = class_getInstanceMethod(cls, originSelector);
    Method swizzledMethod = class_getInstanceMethod(cls, swizzleSelector);
    
    swizzleMethodWithMetaClass(cls, originSelector, originalMethod, swizzleSelector, swizzledMethod);
}

void swizzleMethodWithMetaClass(Class cls, SEL originSelector, Method oriMethod, SEL swizzleSelector, Method swizzledMethod){
    BOOL didAddMethod = class_addMethod(cls,
                                        originSelector,
                                        method_getImplementation(swizzledMethod),
                                        method_getTypeEncoding(swizzledMethod));
    //如果元类有重新函数就替换,否则就交互
    if (didAddMethod) {
        class_replaceMethod(cls,
                            swizzleSelector,
                            method_getImplementation(oriMethod),
                            method_getTypeEncoding(oriMethod));
    } else {
        method_exchangeImplementations(oriMethod, swizzledMethod);
    }
}

@implementation NSObject (IDSwizzleHook)

+ (void)ID_swizzleClassMethod:(SEL)originSelector withSwizzleMethod:(SEL)swizzleSelector {
    swizzleClassMethod(self.class, originSelector, swizzleSelector);
}

- (void)ID_swizzleInstanceMethod:(SEL)originSelector withSwizzleMethod:(SEL)swizzleSelector{
    swizzleInstanceMethod(self.class, originSelector, swizzleSelector);
}

@end

4: 函数方法找不到

1:方法调用流程
runtime中具体的方法调用流程大致如下:

1.首先,在相应操作的对象中的缓存方法列表中找调用的方法,如果找到,转向相应实现并执行。

2.如果没找到,在相应操作的对象中的方法列表中找调用的方法,如果找到,转向相应实现执行

3.如果没找到,去父类指针所指向的对象中执行1,2.

4.以此类推,如果一直到根类还没找到,转向拦截调用,走消息转发机制。

5.如果没有重写拦截调用的方法,程序报错。
1398407-7ee76f8a99cb52e6.jpeg

由上图可见,在一个函数找不到时,runtime提供了三种方式去补救:

1、调用resolveInstanceMethod给个机会让类添加这个实现这个函数

2、调用forwardingTargetForSelector让别的对象去执行这个函数

3、调用forwardInvocation(函数执行器)灵活的将目标函数以其他形式执行。

如果都不中,调用doesNotRecognizeSelector抛出异常。

这里我们选择了第二步forwardingTargetForSelector来做文章。原因如下:

1:resolveInstanceMethod 需要在类的本身上动态添加它本身不存在的方法,这些方法对于该类本身来说冗余的

2:forwardInvocation可以通过NSInvocation的形式将消息转发给多个对象,但是其开销较大,需要创建新的NSInvocation对象,并且forwardInvocation的函数经常被使用者调用,来做多层消息转发选择机制,不适合多次重写

3:forwardingTargetForSelector可以将消息转发给一个对象,开销较小,并且被重写的概率较低,适合重写

对methodSignatureForSelector和 forwardInvocation 进行Hook操作

IDSYNTH_DUMMY_CLASS(NSObject_IDUnrecognizedSelectorHook)

@implementation NSObject (IDUnrecognizedSelectorHook)

+ (void)ID_swizzleUnrecognizedSelector{
    //Class Method
    swizzleClassMethod([self class], @selector(methodSignatureForSelector:), @selector(classMethodSignatureForSelectorSwizzled:));
    swizzleClassMethod([self class], @selector(forwardInvocation:), @selector(forwardClassInvocationSwizzled:));
    
    //Instance Method
    swizzleInstanceMethod([self class], @selector(methodSignatureForSelector:), @selector(methodSignatureForSelectorSwizzled:));
    swizzleInstanceMethod([self class], @selector(forwardInvocation:), @selector(forwardInvocationSwizzled:));
}

+ (NSMethodSignature*)classMethodSignatureForSelectorSwizzled:(SEL)aSelector {
    NSMethodSignature* methodSignature = [self classMethodSignatureForSelectorSwizzled:aSelector];
    if (methodSignature) {
        return methodSignature;
    }
    
    return [self.class checkObjectSignatureAndCurrentClass:self.class];
}

- (NSMethodSignature*)methodSignatureForSelectorSwizzled:(SEL)aSelector {
    NSMethodSignature* methodSignature = [self methodSignatureForSelectorSwizzled:aSelector];
    //正常有方法签名的,按照正常方法调用流程走
    if (methodSignature) {
        return methodSignature;
    }
    
    return [self.class checkObjectSignatureAndCurrentClass:self.class];
}

+ (NSMethodSignature *)checkObjectSignatureAndCurrentClass:(Class)currentClass{
    IMP originIMP = class_getMethodImplementation([NSObject class], @selector(methodSignatureForSelector:));
    IMP currentClassIMP = class_getMethodImplementation(currentClass, @selector(methodSignatureForSelector:));
    
    // 如果重写methodSignatureForSelector就return nil
    if (originIMP != currentClassIMP){
        return nil;
    }

    // 没有方法签名的,没有实现,给出一个我们自定义的签名`v@:@`
    return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}

- (void)forwardInvocationSwizzled:(NSInvocation*)invocation{

}

+ (void)forwardClassInvocationSwizzled:(NSInvocation*)invocation{

}

非主线程刷新UI

- (void)setNeedsLayout;
- (void)setNeedsDisplay;
- (void)setNeedsDisplayInRect:(CGRect)rect;

判断这三个方法是否在主线程执行

- (void)id_HookSetNeedsLayout {
    if ([NSThread isMainThread]) {
        [self id_HookSetNeedsLayout];
    }else{
        dispatch_async(dispatch_get_main_queue(),^{
            [self id_HookSetNeedsLayout];
        });
        [self uploadExceptionInfo];
    }
}

参考文章:https://www.jianshu.com/p/5e2d34f01fba