Runtime 消息传递与转发

一、添加 方法 / 函数 - 动态方法解析(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` 可以将消息同时转发给 `任意多个对象`

这就是两者的最大区别
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348

推荐阅读更多精彩内容