• IOS 消息转发


    最近在看消息转发的资料,发现大部分都是理论知识,很少有完整的代码。现在以代码的形式形象的解释一下:

    用Xcode创建一个工程

    1.正常方法调用

    创建一个类Person 代码如下

    Person.h代码如下:

    #import <Foundation/Foundation.h>
    
    @interface Person : NSObject
    
    - (instancetype)init NS_UNAVAILABLE;
    - (instancetype)initWithUserName:(NSString *)userName;
    
    - (void)logUserName;
    
    @end

    Person.m代码如下:

    #import "Person.h"
    
    @interface Person()
    
    @property (nonatomic, copy) NSString *userName;
    
    @end
    
    @implementation Person
    
    - (instancetype)initWithUserName:(NSString *)userName
    {
        self = [super init];
        if (self) {
            _userName = [userName copy];
        }
        return self;
    }
    
    - (void)logUserName
    {
        NSLog(@"userName = %@", self.userName);
    }
    
    @end

    ViewController.m代码如下:

    #import "ViewController.h"
    #import "Person.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        Person *person = [[Person alloc] initWithUserName:@"小王"];
        [person logUserName];
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end

    运行工程结果为:

    2017-03-01 22:47:07.296 RunTimeDemo[24364:1776826] userName = 小王

    结果正常

    2. unrecognized selector 情况

    把Person.m 的logUserName方法删除,此时Person.m 的代码如下:

    #import "Person.h"
    
    @interface Person()
    
    @property (nonatomic, copy) NSString *userName;
    
    @end
    
    @implementation Person
    
    - (instancetype)initWithUserName:(NSString *)userName
    {
        self = [super init];
        if (self) {
            _userName = [userName copy];
        }
        return self;
    }
    
    @end

    运行工程会出现 如下结果

    2017-03-01 23:06:25.788 RunTimeDemo[24729:1788071] -[Person logUserName]: unrecognized selector sent to instance 0x608000018e00
    2017-03-01 23:06:25.796 RunTimeDemo[24729:1788071] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person logUserName]: unrecognized selector sent to instance 0x608000018e00'

    假如不在Person类中实现logUsrName这个方法,也可以让工程正常运行,此时就涉及到消息转发,Objective-C运行时会给出三次拯救程序崩溃的机会。

    如下:

    (1).Method resolution

    objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发(Message Forwarding)。

    (2).Fast forwarding

    如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。

    (3).Normal forwarding

    这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。

    下面就验证一下上面所对应的函数的执行顺序:

    Person.m的代码如下:

    #import "Person.h"
    
    #define LOG_ClassAndFunctionName  NSLog(@"%@ %s", [self class], __func__)
    
    @interface Person()
    
    @property (nonatomic, copy) NSString *userName;
    
    @end
    
    @implementation Person
    
    - (instancetype)initWithUserName:(NSString *)userName
    {
        self = [super init];
        if (self) {
            _userName = [userName copy];
        }
        return self;
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
        LOG_ClassAndFunctionName;
        return [super resolveInstanceMethod:sel];
    }
    
    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        LOG_ClassAndFunctionName;
        return [super forwardingTargetForSelector:aSelector];
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        LOG_ClassAndFunctionName;
        return [super methodSignatureForSelector:aSelector];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
        LOG_ClassAndFunctionName;
        [super forwardInvocation:anInvocation];
       
    }
    
    - (void)doesNotRecognizeSelector:(SEL)aSelector
    {
        LOG_ClassAndFunctionName;
        [super doesNotRecognizeSelector:aSelector];
    }
    
    @end

    运行结果为:

    2017-03-02 10:16:37.855 RunTimeDemo[35098:2456660] Person +[Person resolveInstanceMethod:]
    2017-03-02 10:16:37.856 RunTimeDemo[35098:2456660] Person -[Person forwardingTargetForSelector:]
    2017-03-02 10:16:37.856 RunTimeDemo[35098:2456660] Person -[Person methodSignatureForSelector:]
    2017-03-02 10:16:37.857 RunTimeDemo[35098:2456660] Person +[Person resolveInstanceMethod:]
    2017-03-02 10:16:37.858 RunTimeDemo[35098:2456660] Person -[Person doesNotRecognizeSelector:]
    2017-03-02 10:16:37.859 RunTimeDemo[35098:2456660] -[Person logUserName]: unrecognized selector sent to instance 0x600000003d40

    运行结果正好验证了上述说明, 最后找不到实现会调用doesNotRecognizeSelector方法

    那如何做才能不让其Crash呢?

    做法是重写刚才说的那几个方法,那我们就倒着重写上面的方法。

    3.重写方法来拯救Crash

    1.在methodSignatureForSelector方法拦截 新建一个类ForwardClass,代码如下,具体看注释

    #import "Person.h"
    
    #define LOG_ClassAndFunctionName  NSLog(@"%@ %s", [self class], __func__)
    
    @interface ForwardClass : NSObject
    
    - (void)logUserName;
    
    @end
    
    @implementation ForwardClass
    
    - (void)forwardLogUserName:(NSString *)userName;
    {
        LOG_ClassAndFunctionName;
        NSLog(@"userName = %@",userName);
    }
    
    @end
    
    @interface Person()
    
    @property (nonatomic, copy) NSString *userName;
    @property (nonatomic, strong) ForwardClass *forwardClass;
    
    @end
    
    @implementation Person
    
    - (instancetype)initWithUserName:(NSString *)userName
    {
        self = [super init];
        if (self) {
            _userName = [userName copy];
            _forwardClass = [[ForwardClass alloc] init];
        }
        return self;
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
        LOG_ClassAndFunctionName;
        return [super resolveInstanceMethod:sel];
    }
    
    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        LOG_ClassAndFunctionName;
        return [super forwardingTargetForSelector:aSelector];
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        LOG_ClassAndFunctionName;
        NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
        if (!signature) {
            /// 方法签名为nil,就生成一个forwardClass的方法签名并制定selector
            signature = [self.forwardClass methodSignatureForSelector:@selector(forwardLogUserName:)];
        }
        return signature;
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
        LOG_ClassAndFunctionName;
        /// 重定向方法
        [anInvocation setSelector:@selector(forwardLogUserName:)];
        /// 假如调用的函数需要参数的话,在这里调用anInvocation的setArgument:atIndex:方法来进行设置; 指定参数,以指针方式,并且第一个参数的起始index是2,因为index为1,2的分别是self和selector
        NSString *userName = self.userName;
        [anInvocation setArgument:&userName atIndex:2];
        /// ForwardClass 接受转发的消息
        [anInvocation invokeWithTarget:[[ForwardClass alloc] init]];
    }
    
    - (void)doesNotRecognizeSelector:(SEL)aSelector
    {
        LOG_ClassAndFunctionName;
    }
    
    @end

    运行结果:

    2017-03-02 10:47:14.229 RunTimeDemo[36473:2479172] Person +[Person resolveInstanceMethod:]
    2017-03-02 10:47:14.229 RunTimeDemo[36473:2479172] Person -[Person forwardingTargetForSelector:]
    2017-03-02 10:47:14.229 RunTimeDemo[36473:2479172] Person -[Person methodSignatureForSelector:]
    2017-03-02 10:47:14.230 RunTimeDemo[36473:2479172] Person +[Person resolveInstanceMethod:]
    2017-03-02 10:47:14.230 RunTimeDemo[36473:2479172] Person -[Person forwardInvocation:]
    2017-03-02 10:47:14.230 RunTimeDemo[36473:2479172] ForwardClass -[ForwardClass forwardLogUserName:]
    2017-03-02 10:47:14.230 RunTimeDemo[36473:2479172] userName = 小王

    此时不Crash了,我们让其执行了forwardClass 中的forwardLogUserName方法。

    2.在forwardingTargetForSelector处拦截,转发给其他的类,执行对应的方法。代码如下

    #import "Person.h"
    
    #define LOG_ClassAndFunctionName  NSLog(@"%@ %s", [self class], __func__)
    
    @interface ForwardClass : NSObject
    
    - (void)logUserName;
    
    @end
    
    @implementation ForwardClass
    
    - (void)forwardLogUserName:(NSString *)userName;
    {
        LOG_ClassAndFunctionName;
        NSLog(@"userName = %@",userName);
    }
    
    - (void)logUserName
    {
        LOG_ClassAndFunctionName;
    }
    
    @end
    
    @interface Person()
    
    @property (nonatomic, copy) NSString *userName;
    @property (nonatomic, strong) ForwardClass *forwardClass;
    
    @end
    
    @implementation Person
    
    - (instancetype)initWithUserName:(NSString *)userName
    {
        self = [super init];
        if (self) {
            _userName = [userName copy];
            _forwardClass = [[ForwardClass alloc] init];
        }
        return self;
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
        LOG_ClassAndFunctionName;
        return [super resolveInstanceMethod:sel];
    }
    
    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        LOG_ClassAndFunctionName;
        id fowarding = [super forwardingTargetForSelector:aSelector];
        if (!fowarding) {
            /// 进行拦截,让ForwardClass的示例进行执行aSelector方法
            fowarding = [[ForwardClass alloc] init];
        }
        return fowarding;
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        LOG_ClassAndFunctionName;
        NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
        if (!signature) {
            /// 方法签名为nil,就生成一个forwardClass的方法签名并制定selector
            signature = [self.forwardClass methodSignatureForSelector:@selector(forwardLogUserName:)];
        }
        return signature;
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
        LOG_ClassAndFunctionName;
        /// 重定向方法
        [anInvocation setSelector:@selector(forwardLogUserName:)];
        /// 假如调用的函数需要参数的话,在这里调用anInvocation的setArgument:atIndex:方法来进行设置; 指定参数,以指针方式,并且第一个参数的起始index是2,因为index为1,2的分别是self和selector
        NSString *userName = self.userName;
        [anInvocation setArgument:&userName atIndex:2];
        /// ForwardClass 接受转发的消息
        [anInvocation invokeWithTarget:[[ForwardClass alloc] init]];
    }
    
    - (void)doesNotRecognizeSelector:(SEL)aSelector
    {
        LOG_ClassAndFunctionName;
    }
    
    @end

    运行结果为:

    2017-03-02 12:35:02.033 RunTimeDemo[41433:2552894] Person +[Person resolveInstanceMethod:]
    2017-03-02 12:35:02.033 RunTimeDemo[41433:2552894] Person -[Person forwardingTargetForSelector:]
    2017-03-02 12:35:02.034 RunTimeDemo[41433:2552894] ForwardClass -[ForwardClass logUserName]

    3.在resolveInstanceMethod处拦截,动态的为类添加相应的方法。代码如下:

    #import "Person.h"
    #import <objc/runtime.h>
    
    #define LOG_ClassAndFunctionName  NSLog(@"%@ %s", [self class], __func__)
    
    @interface ForwardClass : NSObject
    
    - (void)logUserName;
    
    @end
    
    @implementation ForwardClass
    
    - (void)forwardLogUserName:(NSString *)userName;
    {
        LOG_ClassAndFunctionName;
        NSLog(@"userName = %@",userName);
    }
    
    - (void)logUserName
    {
        LOG_ClassAndFunctionName;
    }
    
    @end
    
    @interface Person()
    
    @property (nonatomic, copy) NSString *userName;
    @property (nonatomic, strong) ForwardClass *forwardClass;
    
    @end
    
    @implementation Person
    
    void dynamicMethodIMP(id self, SEL _cmd)
    {
        LOG_ClassAndFunctionName;
    }
    
    - (instancetype)initWithUserName:(NSString *)userName
    {
        self = [super init];
        if (self) {
            _userName = [userName copy];
            _forwardClass = [[ForwardClass alloc] init];
        }
        return self;
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
        LOG_ClassAndFunctionName;
        if (sel == @selector(logUserName)) {
            /// 动态的为这个类去添加方法
            class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    
    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        LOG_ClassAndFunctionName;
        id fowarding = [super forwardingTargetForSelector:aSelector];
        if (!fowarding) {
            /// 进行拦截,让ForwardClass的示例进行执行aSelector方法
            fowarding = [[ForwardClass alloc] init];
        }
        return fowarding;
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        LOG_ClassAndFunctionName;
        NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
        if (!signature) {
            /// 方法签名为nil,就生成一个forwardClass的方法签名并制定selector
            signature = [self.forwardClass methodSignatureForSelector:@selector(forwardLogUserName:)];
        }
        return signature;
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
        LOG_ClassAndFunctionName;
        /// 重定向方法
        [anInvocation setSelector:@selector(forwardLogUserName:)];
        /// 假如调用的函数需要参数的话,在这里调用anInvocation的setArgument:atIndex:方法来进行设置; 指定参数,以指针方式,并且第一个参数的起始index是2,因为index为1,2的分别是self和selector
        NSString *userName = self.userName;
        [anInvocation setArgument:&userName atIndex:2];
        /// ForwardClass 接受转发的消息
        [anInvocation invokeWithTarget:[[ForwardClass alloc] init]];
    }
    
    - (void)doesNotRecognizeSelector:(SEL)aSelector
    {
        LOG_ClassAndFunctionName;
    }
    
    @end

    运行结果为:

    2017-03-02 12:44:24.529 RunTimeDemo[41577:2561853] Person +[Person resolveInstanceMethod:]
    2017-03-02 12:44:24.530 RunTimeDemo[41577:2561853] Person dynamicMethodIMP

    总结:上面三次拦截更加形象的说明消息转发进行的次序。若某个类的实例调用一个没有实现的方法后。首先会调用

    resolveInstanceMethod/resolveClassMethod方法,若没有添加相应的方法,其次就会调用forwardingTargetForSelector方法,若没有转发给其他对象,最后就调用methodSignatureForSelector方法,若方法签名为nil,Runtime则会发出-doesNotRecognizeSelector:消息。此时就Crash了。

    上面的工程链接为: https://github.com/lidaojian/RunTimeDemo

    ================================================================

    若有疑问请加本人QQ:610774281 微信:stephenli225。 一起探讨一起进步。。。。

  • 相关阅读:
    登录远程服务器运行的程序,退出服务器仍然运行的方法
    python爬虫错误
    python将字符转换为字典
    shell中$(( )) 与 $( ) 还有${ }的区别
    JavaBean 开发入门
    反射机制 动态代理
    反射机制的深入应用
    反射机制 反射的应用 ---取得类的结构
    反射机制 CLass类的使用
    JSP 的九大内置对象
  • 原文地址:https://www.cnblogs.com/lidaojian/p/6487110.html
Copyright © 2020-2023  润新知