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