死磕Objective-C runtime运行时之二

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

如何吞掉整个应用的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"];

}

运行时objc_msgSend说过:

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不成功的时候:

  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
    原文作者:天才小飞猫
    原文地址: https://segmentfault.com/a/1190000007446208
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞