实战问题一(答案在最后):
如何吞掉整个应用的unrecognized selector sent to instance
崩溃, 使程序正常运行?
动态添加方法class_addMethod
第一种:
@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"];
}
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:
方法的,参考运行时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
给运行时,告知可以处理该方法。
过程中,我猜测运行时会关注class_addMethod等相关功能代码,然后快速派遣,即使return YES
, 假如此类状态没有任何变化,直接调用doesNotRecognizeSelector:
抛出异常
消息转寄Forwarding
如果运行时objc_msgSend找不到该方法,在抛出异常之前,运行时给我们一个机会转寄(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, 首先之行运行时objc_msgSend
消息转寄(Forwarding)总结:
这里说的是运行时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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。