实战问题一(答案在最后):
如何吞掉整个应用的unrecognized selector sent to instance
崩溃, 使程序正常运行?
动态添加方法class_addMethod
第一种:
@interface ViewController()- (void)hello:(NSString *)content;@endvoid 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"];}
说过:
Bbjc方法只不过是C语言方方法,加上两个特殊的参数第一个是receiver(
self
),第二个参数是selector(_cmd
)
这里我们有个C函数void hello(id self, SEL selector, NSString *content)
,除了上述两个必要参数外,我们添加了个NSString *content
, 然后用class_addMethod
添加,最后一个参数是Objc运行时符号,具体参考, 第一个V代表返回值void
, @代表id
,:代表SEL
,@代表id
(这里是NSString *
)
这里由于在调用[self hello:@"world"]
之时, 运行时方法class_addMethod
添加了hello:
方法的,参考, 完成方法调用
第二种:
@interface ViewController ()- (void)hello:(NSString *)content;@endvoid 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
这回,我们先调用方法,由于通过,无法找到hello:
, 此时运行时会发消息给resolveInstanceMethod:
和resolveClassMethod:
方法,本文因为成员方法调用[[self class] resolveInstanceMethod:@selector(hello:)]
,询问该方法是不是有可能动态实现呢,根据第二种代码实现,发现如果是@selector(hello:)
, 加入动态方法,然后return YES
给运行时,告知可以处理该方法。
过程中,我猜测运行时会关注class_addMethod等相关功能代码,然后快速派遣,即使return YES
, 假如此类状态没有任何变化,直接调用doesNotRecognizeSelector:
抛出异常
消息转寄Forwarding
如果找不到该方法,在抛出异常之前,运行时给我们一个机会转寄(Forwarding)这个消息的机会:
第一种:
@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
实例啦
第二种:
@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, 首先之行 消息转寄(Forwarding)总结:
这里说的是不成功的时候:
resolveClassMethod:
和resolveInstanceMethod
, 若返回YES同时运行时状态有新函数加入,则直接调用实现,完成消息发送若不然,
forwardingTargetForSelector:
若返回不是nil和self,则完成消息发送, 对返回对象进行若不然,
methodSignatureForSelector:
若返回不为空,则发送消息给forwardInvocation:
由Invocation完成若不然, 调用
doesNotRecognizeSelector:
抛出异常
问题一答案:
PS: 本例为Swizzle的正确打开方式,
#import@interface ViewController ()- (void)hello:(NSString *)content;- (void)whoareyou;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; [self hello:@"world"]; [self whoareyou]; }@endIMP __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