美文网首页
Runtime 消息传递与转发

Runtime 消息传递与转发

作者: VampireJune | 来源:发表于2018-11-13 16:09 被阅读0次

一、添加 方法 / 函数 - 动态方法解析(Dynamic Method Resolution)

  • 对象方法 没有 声明实现, 添加 对象方法 / 函数
  • +resolveInstanceMethod:
* 在 对象方法 里面 调
* 此处的方法名要与下面判断时的 一致,有参无参都要一致
[self performSelector:@selector(noInstenceMethod:)];

* 这里一定是 +resolveInstanceMethod:
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@" 1 * resolveInstanceMethod - 方法 %@ * ",NSStringFromSelector(sel));

    * 这里 要与上面的 对象方法调用处,方法名一致,有参无参都要一致
    if (sel == @selector(noInstenceMethod:)) {

        /**
         运行时方法:向指定类中添加特定方法实现的操作
         @param cls 被添加方法的类
         @param name selector方法名 - 直接填此方法带的 sel
         @param imp 指向实现方法的函数指针
         @param types imp函数实现的返回值与参数类型 - 实测,好像不写或写 nil 均可?!
         @return 添加方法是否成功 - 直接写 YES,在这里写就是为了添加方法,干嘛还NO?!
        (但是实测,返回两者,都可运行成功,可手动写代码亲自试验)
        (如果真的返回 NO的话,就关系到【消息转发 - Forwarding】了)
         */
        
        * 哪种添加方法写法 写在上面,就找谁添加的 方法 或 函数 实现 *
        * 写 两种 没用,任选其一 *
        
        * imp 这种写法,只能添加 函数
        // class_addMethod(self, sel, hereInstenceMethod, "v@");

        * imp 这种写法,只能添加 对象方法
        Method addM = class_getInstanceMethod(self, @selector(hereInstenceMethod));
        class_addMethod(self,
                        sel,
                        method_getImplementation(addM),
                        method_getTypeEncoding(addM));

        return YES;
    }

    return [super resolveInstanceMethod:sel];
}

//void hereInstenceMethod()
//{
//    NSLog(@" 2 * hereInstenceMethod 函数 * ");
//}

- (void)hereInstenceMethod
{
    NSLog(@" 2 * hereInstenceMethod - 方法 * ");
}
  • 类方法 没有 声明实现, 添加 类方法 / 函数
  • +resolveClassMethod:
* 类名调用
* 此处的方法名要与下面判断时的 一致,有参无参都要一致
[类名 performSelector:@selector(noClassMethod:)];

* 这里一定是 +resolveClassMethod:
+ (BOOL)resolveClassMethod:(SEL)sel
{
    NSLog(@" 1 * resolveClassMethod + 方法 %@ * ",NSStringFromSelector(sel));

    * 这里 要与上面的 类方法调用处,方法名一致,有参无参都要一致
    if (sel == @selector(noClassMethod:)) {
        
        * 哪种添加方法写法 写在上面,就找谁添加的 方法 或 函数 实现 *
        * 写 两种 没用,任选其一 *
        
        * imp 这种写法,只能添加 函数
        * 注意,这里与 对象方法的添加方法里,self 的写法不同
        // class_addMethod(object_getClass(self), sel, hereClassMethod, "v@");
        
        * imp 这种写法,只能添加 + 方法
        * 注意,这里与 对象方法的添加方法里,self 的写法不同
        * 要这样写:object_getClass(self)   :)
        Method addM = class_getInstanceMethod(self, @selector(hereClassMethod));
        class_addMethod(object_getClass(self),
                        sel,
                        method_getImplementation(addM),
                        method_getTypeEncoding(addM));
        
        return YES;
    }

    return [super resolveClassMethod:sel];
}

//void hereClassMethod()
//{
//    NSLog(@" 2 * hereClassMethod   + 函数 * ");
//}

+ (void)hereClassMethod
{
    NSLog(@" 2 * hereClassMethod   + 方法 * ");
}

注意 + / - 方法的 添加 方法 / 函数,写法有不同

  • class_addMethod方法中的特殊参数“v@”,具体可参考这里
OC Runtime-Type Encodings.png

二、消息转发 - 备用接收者 - 消息接收者重定向

  • 对象方法 在本类中 没有 声明实现,将此消息转发到别的类实现
  • -forwardingTargetForSelector
* 创建一个备用接收者的类 `#import "TestForwardingM.h"`
* 且在此类中实现 `-hereInstanceMethod:`

* 在其他类中 进行`对象方法`调用,引入 `TestForwardingM.h` 头文件
[self performSelector:@selector(hereInstanceMethod:)withObject:@"VampireJune"];

/*
 1. 如果没有实现 `+resolveInstanceMethod`
会直接进入到 `+forwardingTargetForSelector` 

 2. 只要实现了 `+resolveInstanceMethod`
 1> 只要添加了方法成功,就不会去 `+forwardingTargetForSelector`
 无论返回 `YES / NO`
 2> 只要没有添加方法,都会去 `+forwardingTargetForSelector`
 无论返回 `YES / NO / [super resolveInstanceMethod:sel]`
 
 */
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@" 1 * %s  %@ * ",__func__,NSStringFromSelector(sel));
    return [super resolveInstanceMethod:sel];
}

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    NSLog(@" 2 * %s %@ * ", __func__, NSStringFromSelector(aSelector));
    
    if (aSelector == @selector(hereInstanceMethod:)) {
        
        // 返回 `TestForwardingM` 实例对象
        return [[TestForwardingM alloc] init];
    }
    
    return [super forwardingTargetForSelector:aSelector];
}

* `TestForwardingM.m` 中
@implementation TestForwardingM

- (void)hereInstanceMethod:(id)aid
{
    NSLog(@" 3 * %s %@ * ",__func__,aid);
    // 打印
    // 3 * -[TestForwardingM hereInstanceMethod:] VampireJune *
}

@end
  • 类方法 在本类中 没有 声明实现,将此消息转发到别的类实现
  • +forwardingTargetForSelector
* 创建一个备用接收者的类 `#import "TestForwardingM.h"`
* 且在此类中实现 `+hereClassMethod:`

* 在其他类中 进行 `类方法` 调用,引入 `TestForwardingM.h` 头文件
[类名 performSelector:@selector(hereClassMethod:) withObject:@"VampireJune"];

/*
 1. 如果没有实现 `+resolveClassMethod`
 会直接进入到 `+forwardingTargetForSelector`,不能直接敲出此方法
 需要敲出 `-forwardingTargetForSelector`,再将 `-` 改为 `+`
 【实测是这样。2018.11.02(周五),Xcode 10.0(10A255)】
 
 2. 只要实现了 `+resolveClassMethod`
 1> 只要添加了方法且成功,就不会去到 `+forwardingTargetForSelector`
 无论返回 `YES / NO`
 2> 只要没有添加方法,都会去到 `+forwardingTargetForSelector`
 无论返回 `YES / NO / [super resolveClassMethod:sel]`
 */
+ (BOOL)resolveClassMethod:(SEL)sel
{
    NSLog(@" 1 * %s  %@ * ",__func__,NSStringFromSelector(sel));
    return [super resolveClassMethod:sel];
}

+ (id)forwardingTargetForSelector:(SEL)aSelector
{
    NSLog(@" 2 * %s %@ * ", __func__, NSStringFromSelector(aSelector));    

    if (aSelector == @selector(hereClassMethod:)) {

// 返回一个类对象
        return [TestForwardingM class];
    }
    
    return [super forwardingTargetForSelector:aSelector];
}

* `TestForwardingM.m` 中
@implementation TestForwardingM

+ (void)hereClassMethod:(id)aid
{
    NSLog(@" 3 * %s %@ * ",__func__,aid);
    // 打印
    // 3 * +[TestForwardingM hereClassMethod:] VampireJune *
}

@end

三、消息重定向(重定向消息类型,但目标只能重定向给 实例对象)


  • +resolveInstanceMethod:

    -forwardingTargetForSelector:
    两种方法都没能生效时,此时 Runtime 会给这次消息最后一次寻找IMP(方法实现)的机会
  • -methodSignatureForSelector:
  • -forwardInvocation:
* 创建一个备用接收者的类 `#import "TestForwardingM.h"`
* 且在此类中实现 `-hereInstanceMethod:`

* 对象方法调用
[self performSelector:@selector(hereInstanceMethod:)withObject:@"VampireJune"];

* 下面这两个方法可不实现,只这样返回`super`,主要为了最后的发现与总结
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@" 1 * %s  %@ * ",__func__,NSStringFromSelector(sel));
    return [super resolveInstanceMethod:sel];
}

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    NSLog(@" 2 * %s %@ * ", __func__, NSStringFromSelector(aSelector));
    
    return [super forwardingTargetForSelector:aSelector];
}
* 上面这两个方法可不实现

* 下面的方法是 消息重定向 的 `关键操作`  :)

* 这个方法会返回 方法的 `参数和返回值类型`,是一个 `方法签名`
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@" 3 * %s %@ * ", __func__, NSStringFromSelector(aSelector));
    
    if (aSelector == @selector(hereInstanceMethod:)) {
        
        // 如果是带参数的方法,一定要有 `:`
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    return [super methodSignatureForSelector:aSelector];
}

* 如果 `-methodSignatureForSelector:` 返回了 `方法签名`
* `Runtime` 就会创建一个 `NSInvocation` 对象并发送 `-forwardInvocation:` 消息给目标对象。
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL sel = anInvocation.selector;
    NSLog(@"4 * %s %@ * ", __func__, NSStringFromSelector(sel));
    // 打印
    // 4 * -[Art_Market_VC forwardInvocation:] hereInstanceMethod: *
    
    TestForwardingM *tm = [[TestForwardingM alloc] init];

    // 如果 `TestForwardingM` 类的实例对象 可以响应这个方法
    if ([tm respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:tm];
    }else{ // 否则 就崩溃
        [self doesNotRecognizeSelector:sel];
    }
}

* `TestForwardingM.m` 中
@implementation TestForwardingM

- (void)hereInstanceMethod:(id)aid
{
    NSLog(@" - 完整消息转发 * %s %@ * ",__func__,aid);
    // 打印
    // - 完整消息转发 * -[TestForwardingM hereInstanceMethod:] VampireJune *
}

@end

消息重定向 - 发现与总结

 1. 先看打印顺序,会发现 `1 *` 打印了两次,这不是打印 Bug
 
 2018-11-02 16:30:08.977401+0800 [14127:219552]  1 * +[类名 resolveInstanceMethod:]  hereInstanceMethod: *
 
 2018-11-02 16:30:08.977537+0800 [14127:219552]  2 * -[类名 forwardingTargetForSelector:] hereInstanceMethod: *
 
 2018-11-02 16:30:08.977637+0800 [14127:219552]  3 * -[类名 methodSignatureForSelector:] hereInstanceMethod: *
 
 2018-11-02 16:30:08.977760+0800 [14127:219552]  1 * +[类名 resolveInstanceMethod:]  _forwardStackInvocation: *
 
 2018-11-02 16:30:08.977870+0800 [14127:219552] 4 * -[类名 forwardInvocation:] hereInstanceMethod: *
 
 2018-11-02 16:30:08.978527+0800 [14127:219552]  完整消息转发 * -[TestForwardingM hereInstanceMethod:] VampireJune *
 
 2. 如果都实现了下面两个方法
 `+resolveInstanceMethod:` 和 `-forwardingTargetForSelector:`
 而且都 仅仅 返回了 super,没有`添加方法`或`消息转发`的操作
 
 3. 那么 第二次 调 `1 -> +resolveInstanceMethod:` 是
 给 `4 -> -forwardInvocation:` 发消息
 说明就是 给这次消息发送 `最后一次` 寻找IMP的机会,启用完整的消息转发机制
 这就是:消息重定向。
 
4. 从以上的代码中就可以看出 

> `forwardingTargetForSelector` 仅支持 `一个对象` 的返回,也就是说消息只能被转发给 `一个对象`
> 而 `forwardInvocation` 可以将消息同时转发给 `任意多个对象`

这就是两者的最大区别

相关文章

  • iOS - Runtime - 概念和方法交换

    runtime的概述runtime的相关概念runtime消息机制消息传递动态方法解析消息转发runtime的作用...

  • runtime底层实现原理

    一、Runtime介绍二、Runtime源码初探三、Runtime消息传递四、Runtime消息转发五、Runti...

  • Runtime 消息传递与转发

    一、添加 方法 / 函数 - 动态方法解析(Dynamic Method Resolution) 对象方法 没有...

  • runtime消息传递与转发

    官方文档及资源地址Documentation Archive[https://developer.apple.co...

  • Runtime 的应用

    前面我们说到:Runtime 消息传递机制Runtime 消息转发机制Runtime 交换方法今天我们来谈谈Run...

  • iOS消息转发机制

    消息转发机制: 消息转发机制是相对于消息传递机制而言的。 1、消息(传递)机制 RunTime简称运行时。就是系统...

  • Runtime梳理(一)消息机制及应用

    Runtime的介绍 Runtime消息的传递和转发 Runtime的应用 1.Runtime的介绍 Object...

  • iOS面试之Runtime大全

    Runtime内容如下: 数据结构 类对象与元类对象 消息传递 方法缓存 消息转发 Method-Swizzlin...

  • iOS面试之Runtime模块

    Runtime内容如下: 数据结构 类对象与元类对象 消息传递 方法缓存 消息转发 Method-Swizzlin...

  • Runtime相关

    Runtime 数据结构 类对象与元类对象 消息传递 方法缓存 消息转发 Method-Swizzling 动态添...

网友评论

      本文标题:Runtime 消息传递与转发

      本文链接:https://www.haomeiwen.com/subject/lkotxqtx.html