无论是全栈大神,还是普通程序猿,都不能规避掉所有的异常,所以只要是人写的程序都有意外奔溃的可能,更何况操作系统本身就可能会包含一些意想不到的异常情况.随着开发经验和阅历的增加,由于自身能力问题导致的异常情况会越来越少.但异常不可避免,而应用闪退又是一种极其糟糕的用户体验,所以应该尽可能地减少甚至消除这些异常导致的闪退现象.
今天重点讨论一下关于
unrecognized selector sent to instance 0x......
的处理.
这是一个经常会遇到的异常,造成这个异常的情况的最终原因是调用了当前类/对象没有实现的方法.而直接原因可能但不限于:
直接强转了本不属于强转类的对象,然后调用属于强转对象的方法;边界条件判断缺失导致调用不属于当前对象的方法;遵循协议但未实现协议方法;其他导致调用了当前对象(类是一种特殊的对象)未实现的方法.
为了消除这种异常导致的应用闪退,可以尝试使用消息转发机制进行处理.对于消息转发机制时机的使用,一般会选择在消息转发的最后阶段做处理,也就是标准消息转发(normal forwarding)
- (void)forwardInvocation:(NSInvocation *)anInvocation;
来做处理.选择这个时机的主要原因是:
这是消息转发机制的最后一步,能够执行到这里就说明这个异常并没有在之前的时机中被处理,防止拦截到了系统将要处理的异常(系统也会利用消息转发机制做一些系统级的异常处理,这些异常并不是我们要处理的);这个方法中包含了方法可以执行的完整信息,最方便做方法相关转发等处理.
在进入到该时机之前系统会尝试调用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
如果返回了可用的方法签名,才会进入到标准消息转发流程,否则会直接调用doesNotRecognizeSelector抛出异常导致闪退.这就提供了一种可以消除由于调用未实现的方法导致的应用闪退思路.
基本思路:
使用运行时替换根类的methodSignatureForSelector:方法,如果该方法可以返回有效的方法签名,则证明调用的方法实现是存在的,直接放回方法签名;否则就直接返回一个已经自定义的实现签名;由于当前对象/类并没有实现对应的方法,所以会进入forwardInvocation:方法,这时就可以执行一个自定义的方法来防止应用闪退.
实现
定义ExceptionHandler类,用来接收未实现的方法,将未实现的方法都转交到ExceptionHandler中处理.
@interface ExceptionHandler : NSObject
+ (void)noSelector;
- (void)noSelector;
@end
@implementation ExceptionHandler
+ (void)noSelector {}
- (void)noSelector {}
@end
定义UnrecognizedSelectorHandler类,封装用来处理由于未实现对应方法而导致的异常.
@interface UnrecognizedSelectorHandler : NSObject
+ (void)start;
@end
@implementation UnrecognizedSelectorHandler
//保存类方法的methodSignatureForSelector原始实现
NSMethodSignature * (*ori_meta_methodSignatureForSelector)(id self, SEL _cmd, SEL aSelector);
//保存实力方法的methodSignatureForSelector原始实现
NSMethodSignature * (*ori_methodSignatureForSelector)(id self, SEL _cmd, SEL aSelector);
NSMethodSignature * methodSignatureForSelector(id self, SEL _cmd, SEL aSelector) {
Class class = object_getClass(self);
if (class_isMetaClass(class)) {
//类方法
NSMethodSignature *signature = ori_meta_methodSignatureForSelector(self, _cmd, aSelector);
if (signature) {
//若类方法已经实现则直接返回
return signature;
}
} else {
//实例方法
NSMethodSignature *signature = ori_methodSignatureForSelector(self, _cmd, aSelector);
if (signature) {
//若实例方法已经实现则直接返回
return signature;
}
}
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
//保存元类中的forwardInvocation实现
void (*ori_meta_forwardInvocation)(id self, SEL _cmd, NSInvocation * anInvocation);
//保存普通类中的forwardInvocation实现
void (*ori_forwardInvocation)(id self, SEL _cmd, NSInvocation * anInvocation);
void ds_forwardInvocation(id self, SEL _cmd, NSInvocation * anInvocation) {
Class class = object_getClass(anInvocation.target);
BOOL existImp = class_respondsToSelector(anInvocation.class, anInvocation.selector);
if (class_isMetaClass(class)) {
if (existImp && ori_meta_forwardInvocation) {
ori_meta_forwardInvocation(self, _cmd, anInvocation);
} else {
//元类,类方法
anInvocation.target = [ExceptionHandler class];
anInvocation.selector = @selector(noSelector);
[anInvocation invoke];
}
} else {
if (existImp && ori_forwardInvocation) {
ori_forwardInvocation(self, _cmd, anInvocation);
} else {
id obj = [[ExceptionHandler alloc] init];
anInvocation.target = obj;
anInvocation.selector = @selector(noSelector);
[anInvocation invoke];
}
}
}
+ (void)start {
//1. 处理类方法异常
Class class = [NSObject class];
Method method = class_getInstanceMethod(class, @selector(methodSignatureForSelector:));
ori_methodSignatureForSelector = (NSMethodSignature *(*)(id,SEL, SEL))method_setImplementation(method,(IMP)methodSignatureForSelector);
method = class_getInstanceMethod(class, @selector(forwardInvocation:));
ori_forwardInvocation = (void(*)(id,SEL,NSInvocation *))method_setImplementation(method, (IMP)ds_forwardInvocation);
//2. 处理实例方法异常
class = object_getClass(class);
Method method_meta = class_getInstanceMethod(class, @selector(methodSignatureForSelector:));
ori_meta_methodSignatureForSelector = (NSMethodSignature *(*)(id,SEL, SEL))method_setImplementation(method_meta,(IMP)methodSignatureForSelector);
method_meta = class_getInstanceMethod(class, @selector(forwardInvocation:));
ori_meta_forwardInvocation = (void(*)(id,SEL,NSInvocation *))method_setImplementation(method_meta, (IMP)ds_forwardInvocation);
}
@end
为了在尽可能早地时机拦截处理异常,可以将这个方法放置在应用启动方法中:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//注册异常处理
[UnrecognizedSelectorHandler start];
return YES;
}
来来来,开始测试啦:
//为了方便测试创建Person类
@interface Person : NSObject
//以下所有方法只声明不做实现
+ (void)sayHello;
+ (NSString *)getContent;
+ (Person *)sendContent:(NSString *)content;
- (void)sayHello;
- (NSString *)getContent;
- (Person *)sendContent:(NSString *)content;
@end
@implementation Person
@end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//注册异常处理,一定要尽可能早地注册才能尽可能多地拦截到异常
[UnrecognizedSelectorHandler start];
NSString *str = [NSNull null];
if (str.length > 0) {
NSLog(@"str合法可用");
} else {
NSLog(@"str不合法可用");
}
[Person sayHello];
NSString *classContent = [Person getContent];
NSLog(@"class content == %@", classContent);
Person *classPerson = [Person sendContent:@"Hello world"];
NSLog(@"class person == %@", classPerson);
Person *person = [[Person alloc] init];
[person sayHello];
NSString *content = [person getContent];
NSLog(@"instance content == %@", content);
Person *instancePerson = [person sendContent:@"Hello world"];
NSLog(@"class person == %@", instancePerson);
return YES;
}
可以看到,在注册完成异常处理类UnrecognizedSelectorHandler的start方法之后,凡是unrecognized selector类的异常都被很好地拦截处理,从而使得应用不再发生闪退.