死磕Objective-C runtime运行时之二

实战问题一(答案在最后):

如何吞掉整个应用的unrecognized selector sent to instance崩溃, 使程序正常运行?html

动态添加方法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"];

}

运行时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

消息转寄Forwarding

若是运行时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对象

消息转寄(Forwarding)总结:

这里说的是运行时objc_msgSend不成功的时候:

  1. resolveClassMethod:resolveInstanceMethod, 若返回YES同时运行时状态有新函数加入,则直接调用实现,完成消息发送

  2. 若否则, forwardingTargetForSelector: 若返回不是nil和self,则完成消息发送, 对返回对象进行运行时objc_msgSend

  3. 若否则, methodSignatureForSelector: 若返回不为空,则发送消息给forwardInvocation:由Invocation完成

  4. 若否则, 调用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
相关文章
相关标签/搜索