拯救即将崩溃代码之Objective-C消息转发
来源:互联网 发布:故宫淘宝是故宫开的吗 编辑:程序博客网 时间:2024/04/29 20:43
日常开发中,有时调用对象的某个方法,会出现异常,如下:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ViewController doSomething:]: unrecognized selector sent to instance 0x7fcd815bc580'
意思是在类ViewController中调用doSomething方法,但是没有找到该方法,程序异常,然后崩溃。
对于这种情况,有没有什么补救措施防止程序崩溃呢?
答案是有,方式大致有几种:
1、用try...catch来包住可能会出问题的代码;
2、调方doSomething方面前先用respondsToSelector来判断有无指定的方法,有再调用;
3、NSSetUncaughtExceptionHandler机制来控制处理;
4、使用Objective-C Runtime的消息转发机制来处理。
处理方式说明1用try...catch来包住可能会出问题的代码; @try {<#Code that can potentially throw an exception#>
}
@catch (NSException *exception) {
<#Handle an exception thrown in the @try block#>
}
@finally {
<#Code that gets executed whether or not an exception is thrown#>
}2调方doSomething方面前先用respondsToSelector来判断有无指定的方法,有再调用;if ([类的实例对象 respondsToSelector:@selector(doSomething:)]) {
[类的实例对象 doSomething:@"xxx"];
}3NSSetUncaughtExceptionHandler机制来控制处理;NSSetUncaughtExceptionHandler(&ocExceptionHandle);
void ocExceptionHandle(NSException *exception)
{
//...
}4使用Objective-C的消息转发机制来处理。1.+ resolveInstanceMethod:(SEL)sel // 对应实例方法
或者 + resolveClassMethod:(SEL)sel // 对应类方法
2.- (id)forwardingTargetForSelector:(SEL)aSelector
3.- (void)forwardInvocation:(NSInvocation *)anInvocation
说明下:
对于方法1,try...catch可以抓住上述NSInvalidArgumentException异常。try...catch适用于未来不可知的情况,但是对于知道可能会有问题的地方,还是需要编码时加强健壮性,就像上面的方法2,respondsToSelector就是用来判断是否存在指定的方法,如果存在再调用就不会发生错误。
对于方法3,适用于我们没有写try...catch的代码,如果发生异常,就会被我们指定的异常Handler抓住,这个时候就可以做相关处理,比如将异常信息记日志、发送服务器或者忽略等操作。
而方法4,消息转发(Message Forward),是Objective-C语言级别的特性,即Runtime的特性,也是Objective-C动态语言的特色之一。Objective-C Runtime会提供3次挽救的机会(准确的说是1次动态方法解析+2次消息转发;其中实例方法救3次,类方法救1次):
正式名称通俗理解实例方法类方法1Dynamic Method Resolution(动态方法解析)第一次挽救:Runtime说给我一个可以用的替代方法。有有2Fast Forwarding
(快速消息转发,指定备用接受者)第二次挽救:Runtime说告诉我谁可以搞定这个事。有 3Normal Message Forward
(消息转发)第三次挽救:Runtime说我把这个烂事包装成NSInvocation给你,你看着办吧。有
补充一点:
1、Objective-C实例对象调用一个方法,会首先在本类方法列表查找,如果没有,会在父类再查找,直到根类NSObject,在任何一层找到方法,则执行,如果到了最后根类NSObject还没有找到,才会触发Objective-C Runtime的消息转发机制。
2、Objective-C类对象调用一个方法,会首先在本类的元类方法列表中查找,如果没有,会在元类的父类再查找,直到根类NSObject的元类,在任何一层找到方法,则执行,如果到了最后根类NSObject元类还没有找到,才会触发Objective-C Runtime的消息转发机制。
(关于Objective-C的类、元类相关知识可以参考我的另一篇文章:Objective-C类之关系)
例子:
有个类ClassA,现在打算调用一个方法doSomething:(NSString *)val,如果这个doSomething方法不存在就会报异常然后程序崩溃;如果在消息转发的阶段我们做了处理就会挽救灾难于崩溃之前。
1、对于实例方法的完整的处理流程如下:2、对于类方法的完整的处理流程如下:
代码例子如下:
ViewController.m#import "ViewController.h"#import <objc/runtime.h>#import <objc/message.h>#import "Test1.h"@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. /////////////////////////// 实例方法 /////////////////////////// // 1 调用不存在的实例方法“doSomething” // 在 resolveInstanceMethod 方法内提供了解救方案,即动态添加了一个方法作为实例方法“doSomething”的替代。 SEL sel1 = NSSelectorFromString(@"doSomething:"); [self performSelector:sel1 withObject:nil]; // 2 调用不存在的实例方法“doSomething2” // 在 forwardingTargetForSelector 方法内提供了解救方案,即将实例化的Test1类对象return给了调用, SEL sel2 = NSSelectorFromString(@"doSomething2"); [self performSelector:sel2 withObject:nil]; // 3 调用不存在的实例方法“doSomething3” // 在 forwardInvocation 方法内提供了解救方案,即将实例化的Test1类对象return给了调用, SEL sel3 = NSSelectorFromString(@"doSomething3:"); NSInteger b = 88; NSValue *value3 = [NSValue value:&b withObjCType:@encode(NSInteger)]; [self performSelector:sel3 withObject:value3]; //??传递的参数类型最后Test1里不对。 /////////////////////////// 类方法 /////////////////////////// // 4 调用不存在的类方法"doClassMethod1" // 在 resolveClassMethod 方法内提供了解救方案,即动态添加了一个方法作为类方法“doClassMethod1”的替代。 SEL sel4 = NSSelectorFromString(@"doClassMethod1:"); [ViewController performSelector:sel4 withObject:@"aaa"]; }- (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated.}+ (BOOL)resolveClassMethod:(SEL)sel { NSLog(@" >> Class resolving %@", NSStringFromSelector(sel)); if (sel == @selector(doClassMethod1:)) { IMP imp = imp_implementationWithBlock(^(id self, NSString *val) { NSLog(@">>> call doClassMethod1, into block, param=%@", val); }); Class metaClass = object_getClass(self); class_addMethod(metaClass, sel, imp, "v@:@@"); return YES; } BOOL rev = [super resolveClassMethod:sel]; return rev;}+ (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(doSomething:)) {// class_addMethod([self class], sel, imp_implementationWithBlock(^(id self, NSString *str) {// NSLog(@"block, val=%@", str);// }), "v@"); class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:@"); } return [super resolveInstanceMethod:sel];}- (id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(doSomething2)) { return [Test1 new]; //Test1.h中没有申明,但Test1.m中有方法就可以。 } return [super forwardingTargetForSelector:aSelector];}/* -forwardInvocation:在一个对象无法识别消息之前调用,再需要重载-methodSignatureForSelector:,因为在调用-forwardInvocation:之前是要把消息打包成NSIvocation对象的,所以需要-methodSignatureForSelector:重载,如果不能在此方法中不能不为空的NSMethodSignature对象,程序依然会崩溃 */- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ if (aSelector == @selector(doSomething3:)) { NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector]; if (!methodSignature) { if ([Test1 instancesRespondToSelector:aSelector]) { methodSignature = [Test1 instanceMethodSignatureForSelector:aSelector]; } } return methodSignature; } return nil;}- (void)forwardInvocation:(NSInvocation *)anInvocation { if (anInvocation.selector == @selector(doSomething3:)) { Test1 *test1 = [Test1 new]; if ([test1 respondsToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:test1]; } }}#pragma mark - Custom Methodvoid dynamicMethodIMP(id self, SEL _cmd, NSString *str) { NSLog(@"into dynamicMethodIMP, param=%@", str); }@end
Test1.m
#import "Test1.h"@implementation Test1- (void)doSomething2 { NSLog(@"into Test1, call doSomething2");}- (void)doSomething3:(NSValue*)val { NSInteger value; [val getValue:&value]; NSLog(@"into Test1, call doSomething3, param=%ld", value);}+ (void)doClassMethod1:(NSString*)val { NSLog(@"into Test1, call doClassMethod1, param=%@", val);}@end
- 拯救即将崩溃代码之Objective-C消息转发
- Objective-C之消息转发
- 轻松学习之 Objective-C消息转发
- 轻松学习之 Objective-C消息转发
- 轻松学习之 Objective-C消息转发
- Objective-C 消息转发
- Objective-C 消息转发
- Objective-C 消息转发
- Objective-C 消息转发
- Objective-C消息转发
- Objective-C消息转发
- Objective-C消息转发
- Objective-C消息转发
- objective-c消息转发机制
- Objective-C 消息转发 详解
- Objective-c 消息转发机制
- Objective-C 消息转发机制
- Objective-C runtime之消息转发机制(三)
- 机器学习基石(林軒田)笔记之十三
- [转载]Photoshop背景图制作 : 星空幻想
- contrastive loss function (papers)
- hdu5876 Sparse Graph(补图最短路)
- 有符号数除以-2的幂
- 拯救即将崩溃代码之Objective-C消息转发
- 360笔试题
- linux命令大全
- HDU5873 football game
- Hibernate
- php基础之预定义变量
- Java Web dev搭建经验总结
- 初探UCOSII
- 秒杀多线程学习笔记