ios项目如何避免crash

随着app不断的迭代,代码会变得越来越多,经过N个人的持续N年的代码,维护起来越来越难,也很难保证测试case覆盖所有场景。举一个例子:如果原来服务器返回的是数组,而现在返回字符串,如果代码上没有添加判断,很可能就会造成“unrecognized selector sent to instance”,谁也不知道当初这么写的逻辑,没人敢动老代码。几万甚至数十万行的代码,这样的风险不可避免。

我的希望就是尽量对源代码少的改动,增加程序的健壮性,避免由于异常情况导致的crash。目前主要用到的原理就是通过方法替换和消息转发的机制,避免因为程序缺少判断而导致crash。

消息转发大家并不陌生,如果遇到unrecognized selector sent to instance,我可以通过消息转发捕捉的这个情况,而且因为很多框架已经使用了这个机制,所以尽量避免与其它框架的冲突(如JSPatch)

- (NSMethodSignature *)swizz_instance_methodSignatureForSelector:(SEL)aSelector {
    
    //默认先调用原始方法,判断用户有没有对此进行处理,如果没有就会unrecognized异常,这里就要加上处理
    NSMethodSignature *sig = [self swizz_instance_methodSignatureForSelector:aSelector];
    if (sig == nil) {
        if (![[LongCrashManager sharedInstancel] respondsToSelector:aSelector]) {
            //动态的给一个实例添加一个方法,去处理这个unrecognized selector
            class_addMethod([LongCrashManager class], aSelector, (IMP)dynamicMethodIMP, "v@:");
        }
        sig = [[LongCrashManager sharedInstancel] methodSignatureForSelector:aSelector];
        [[LongCrashManager sharedInstancel] onCrashWithInfo:[NSString stringWithFormat:@"LongCrash|[%@ %@]:unrecognized selector sent to class %p|instance" ,[self class] ,NSStringFromSelector(aSelector) ,self]];
    }
    return sig;
}

- (void)swizz_instance_forwardInvocation:(NSInvocation *)aInvocation
{
    id target = nil;
    if ([[LongCrashManager sharedInstancel] methodSignatureForSelector:[aInvocation selector]] ) {
        target = [LongCrashManager sharedInstancel];
        [aInvocation invokeWithTarget:target];
    } else {
        [self swizz_instance_forwardInvocation:aInvocation];
    }
}

动态添加的方法:

void dynamicMethodIMP(id self, SEL _cmd) {
    //do nothing
}

方法替换:

+ (void)long_crash
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        method_exchangeImplementations(class_getInstanceMethod([NSObject class], @selector(forwardInvocation:)), class_getInstanceMethod([self class], @selector(swizz_instance_forwardInvocation:)));
        
        method_exchangeImplementations(class_getInstanceMethod([NSObject class], @selector(methodSignatureForSelector:)), class_getInstanceMethod([self class], @selector(swizz_instance_methodSignatureForSelector:)));
        
        method_exchangeImplementations(class_getClassMethod([NSObject class], @selector(forwardInvocation:)), class_getClassMethod([self class], @selector(swizz_class_forwardInvocation:)));
        
        method_exchangeImplementations(class_getClassMethod([NSObject class], @selector(methodSignatureForSelector:)), class_getClassMethod([self class], @selector(swizz_class_methodSignatureForSelector:)));
    });
}

如果到methodSignatureForSelector程序本身还没有处理,这就意味着很有可能导致crash了,为了避免这种情况,我找了一个实例动态添加了这个selector,去接收处理这条消息,通过回调反馈给应用,告诉开发者具体crash的原因。初始化LongCrashManager的时候我会替换NSObject的对应的实例方法和类方法(两个必须都要替换,调用类方法也可能会遇到这种情况),因为OC中所有的类都继承NSObject,所以只要很小的代码改动,就避免了程序因为异常case导致的“unrecognized selector sent to instance”。

至于对性能上的担心,我觉得应该可以忽略,毕竟大部分情况是走不到这里的,而且目前对于正常App来讲,避免Crash更为重要。比如千万日活的APP,因为服务异常导致的数据错误,同时老代码没有针对这一case添加判断,成千上万的crash对于App简直就是致命的

 

上述提到的代码:https://github.com/lizilong1989/LongCrash

 

posted @ 2017-12-01 11:19  shuffle  阅读(902)  评论(0编辑  收藏  举报