如何吞掉整个应用的unrecognized selector sent to instance
崩溃, 使程序正常运行?html
@interface ViewController() - (void)hello:(NSString *)content; @end void hello(id self, SEL selector, NSString *content){ NSLog(@"hello %@", content); } @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. class_addMethod([self class], @selector(hello:), (IMP)hello, "v@:@"); [self hello:@"world"]; }
运行时objc_msgSend说过:ios
Bbjc方法只不过是C语言方方法,加上两个特殊的参数第一个是receiver(
self
),第二个参数是selector(_cmd
)app
这里咱们有个C函数void hello(id self, SEL selector, NSString *content)
,除了上述两个必要参数外,咱们添加了个NSString *content
, 而后用class_addMethod
添加,最后一个参数是Objc运行时符号,具体参考这里, 第一个V表明返回值void
, @表明id
,:表明SEL
,@表明id
(这里是NSString *
)ide
这里因为在调用[self hello:@"world"]
之时, 运行时方法class_addMethod
添加了hello:
方法的,参考运行时objc_msgSend, 完成方法调用函数
@interface ViewController () - (void)hello:(NSString *)content; @end void hello(id self, SEL selector, NSString *content){ NSLog(@"hello %@", content); } @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. [self hello:@"world"]; } + (BOOL)resolveInstanceMethod:(SEL)sel{ if (sel == @selector(hello:)) { class_addMethod([self class], sel, (IMP)hello, "v@:@"); return YES; } return [super resolveInstanceMethod:sel]; } @end
这回,咱们先调用方法,因为经过运行时objc_msgSend,没法找到hello:
, 此时运行时会发消息给resolveInstanceMethod:
和resolveClassMethod:
方法,本文由于成员方法调用[[self class] resolveInstanceMethod:@selector(hello:)]
,询问该方法是否是有可能动态实现呢,根据第二种代码实现,发现若是是@selector(hello:)
, 加入动态方法,而后return YES
给运行时,告知能够处理该方法。ui
过程当中,我猜想运行时会关注class_addMethod等相关功能代码,而后快速派遣,即便return YES
, 假如此类状态没有任何变化,直接调用doesNotRecognizeSelector:
抛出异常spa
若是运行时objc_msgSend找不到该方法,在抛出异常以前,运行时给咱们一个机会转寄(Forwarding)这个消息的机会:code
@interface SomeFool: NSObject - (void)hello:(NSString *)content; @end @implementation SomeFool - (void)hello:(NSString *)content{ NSLog(@"hello %@", content); } @end @interface ViewController (){ SomeFool *_surrogate; } - (void)hello:(NSString *)content; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self hello:@"world"]; } - (id)forwardingTargetForSelector:(SEL)aSelector{ if (!_surrogate) { _surrogate = [SomeFool new]; } return _surrogate; } @end
第一次机会是运行时询问forwardingTargetForSelector:
是否是这个方法其余人可以处理呢?代码forwardingTargetForSelector:
返回SomeFool
实例,运行时就不会抱怨,把消息传给SomeFool
实例啦htm
@interface SomeFool: NSObject - (void)hello:(NSString *)content; @end @implementation SomeFool - (void)hello:(NSString *)content{ NSLog(@"hello %@", content); } @end @interface ViewController (){ SomeFool *_surrogate; } - (void)hello:(NSString *)content; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; _surrogate = [SomeFool new]; [self hello:@"world"]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSMethodSignature* signature = [super methodSignatureForSelector:aSelector]; if (!signature) { signature = [_surrogate methodSignatureForSelector:aSelector]; } return signature; } - (void)forwardInvocation:(NSInvocation *)anInvocation{ if ([_surrogate respondsToSelector: [anInvocation selector]]){ [anInvocation invokeWithTarget:_surrogate]; } else{ [super forwardInvocation:anInvocation]; } }
第二次机会是运行时询问methodSignatureForSelector:
是否是这个方法其余人有具体实现呢?代码中咱们把SomeFool
将具体实现返回,而后运行时就会调用forwardInvocation:
。在其中,咱们用[anInvocation invokeWithTarget:_surrogate]
调用方法。直接将消息从新跑给SomeFool, 首先之行运行时objc_msgSend对象
这里说的是运行时objc_msgSend不成功的时候:
resolveClassMethod:
和resolveInstanceMethod
, 若返回YES同时运行时状态有新函数加入,则直接调用实现,完成消息发送
若否则, forwardingTargetForSelector:
若返回不是nil和self,则完成消息发送, 对返回对象进行运行时objc_msgSend
若否则, methodSignatureForSelector:
若返回不为空,则发送消息给forwardInvocation:
由Invocation完成
若否则, 调用doesNotRecognizeSelector:
抛出异常
PS: 本例为Swizzle的正确打开方式,详情
#import <objc/runtime.h> @interface ViewController () - (void)hello:(NSString *)content; - (void)whoareyou; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self hello:@"world"]; [self whoareyou]; } @end IMP __original_forwardInvocation = NULL; IMP __original_methodSignatureForSelector = NULL; void __swizzle_forwardInvocation(id self, SEL _cmd, NSInvocation * anINvocation){ //pretend nothing happen } NSMethodSignature * __swizzle_methodSignatureForSelector(id self, SEL _cmd, SEL aSelector){ return ((NSMethodSignature *(*)(id, SEL, SEL))__original_methodSignatureForSelector)(self, _cmd, NSSelectorFromString(@"cacheAll")); } @interface NSObject(cacheAll) @end @implementation NSObject(cacheAll) + (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method oringal_method = class_getInstanceMethod([self class], @selector(forwardInvocation:)); __original_forwardInvocation = method_setImplementation(oringal_method, (IMP)__swizzle_forwardInvocation); oringal_method = class_getInstanceMethod([self class], @selector(methodSignatureForSelector:)); __original_methodSignatureForSelector = method_setImplementation(oringal_method, (IMP)__swizzle_methodSignatureForSelector); }); } - (void)cacheAll{} @end