如何吞掉整個應用的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