Objective-c中的对象间的消息传递以及消息路由

  刚开始使用Objective-C时,总是习惯将对象间发送消息之间称呼为方法调用。心想,这和c#不是一回事吗?不就是调用实例方法吗,还搞个消息发送作甚,最后还不是要转化为方法的调用?通过一段时间的理解学习,觉得并不是消息调用那么简单,才体会到了水果公司的强大。

  在现实生活中,每一个人包括工具都是一个独立的个体,假如张三是顾客(Customer),李四是某一个店的普通的技工(Artisan),当张三需要李四做一个特殊的板凳时,一般情况下,张三会直接跟李四说:喂,四哥,给我做一个某某功能的板凳吧!

  好了,回到正题,不知道看到上面这段场景,然后联想一下我们程序的世界,有没有什么想法?不管大家有没有,反正我是有的。下面将我个人的想法简单描述一下:我们可以将张三和李四都看作是程序中的对象,李四作板凳看做时李四对象上的一个实例方法。先说说C#中是什么样子,代码如下:

private void test()//Customer中的一个方法
{
    Artisan lisi = new Artisan();
    lisi.MakeBanDeng();
}

  表面看起来很合乎情理,其实从现实的角度讲并不合理。为什么这么说呢?李四的确是有个技能是makeBanDeng,通过上述直接调用的方式展示的是张三让一定要让李四亲自做板凳。可是有时候事情并不是这样。会有如下几种情况:

1、李四不会做板凳,张三只是个人认为李四会做板凳,多么的一厢情愿啊。

2、李四不会做板凳,虽然张三让李四做板凳,但是李四认识很多人,知道有的人会做,可以交给其它人做,最后给张三,仿佛是自己做的一样。

3、李四不会做板凳,也不认识会做板凳的人,板凳店经理知道了这个事,决定亲自处理它。

4、李四会做板凳,这没有什么好说的,那就直接做得了。

  既然有四种情况,那么怎么能简单的通过直接让李四做板凳来完成呢?而类似C#这样的通过方法调用无一不是实现的第四种,前面三种都不予考虑,仿佛张三让李四做板凳就一定是李四做似的。

  接下来我们看看在Objective-c中是如何实现的呢?Objective-c把方法的调用以消息通信代替,张三让李四做板凳这件事在Objective-c中是通过张三告诉李四做一个板凳这个消息来完成,这是不是仿佛更符合现实情况?这就是Objective-C中的消息传递,就像对象间交流一样,更自然更容易让人接受。

  既然我们知道了Objective-C中对象间是采用消息传递来完成互动,那么它们内部有什么机制呢?下面我来一一揭开。

  还是用上面的例子来说明,当上面的第一种情况出现的时候,也就是当 Objective-C中的对象收到了一个没有相应的方法来响应的消息的时候,OC会通知该对象解析出实例方法,这个消息对应的selector名称为resolveInstanceMethod:。我们称之为动态方法解析:

一、动态方法解析相关代码及说明

  下面是Person的头文件Person.h,里面只有一个方法makeMaterial。

//
//  Person.h

#import <Foundation/Foundation.h>

@class Material;

@interface Person : NSObject

- (Material *)makeMaterial;

@end

下面是主程序调用代码,告知李四做板凳。

#import <Foundation/Foundation.h>
#import "Material.h"
#import "Person.h"

int main(int argc, const char * argv[]){
    Person *lisi = [[Person alloc] init];
    Material *bandeng = [lisi performSelector:@selector(makeBanDeng)];
    NSLog(@"%@",bandeng);
}

  我们知道在李四所属类Person.h中是没有makeBanDeng这个方法的,默认情况下一定会找不到该方法,下面是利用resolveInstanceMethod:来处理该问题:

//
//  Person.m
//  Created by 084221019 on 15/9/19.
//  Copyright © 2015年 forwk1990. All rights reserved.
//

#import "Person.h"
#import "Material.h"
@import ObjectiveC;

@implementation Person

id makeBanDeng(id self,SEL _cmd){
    return [[Material alloc] initWithDesc:@"li si make a bandeng"];
};

- (Material *)makeMaterial{
    Material *material = [[Material alloc] initWithDesc:@"green"];
    return material;
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSString *selectorString = NSStringFromSelector(sel);
    if([selectorString compare:@"makeBanDeng"] == NSOrderedSame){
        class_addMethod(self,sel,(IMP)makeBanDeng,"@@:");
        return YES;
    }else{
        return [super resolveInstanceMethod:sel];
    }
}

@end

  我们看到在resolveInstanceMethod中,为该对象类新增了一个实例方法叫makeBanDeng。这就好比,张三叫李四做,但是李四不会做,这个时候李四选择自我学习做板凳,学成后(return YES),照样可以做板凳。下面是程序的运行结果:

  如果动态方法解析中并没有解析到合适的方法,也就是之前描述的第二种情况,李四决定找其它人来做。这个在Objective-C中叫消息的备援接收。selector名称为forwardingTargetForSelector:

二、消息的备援接受者的相关代码及说明

  新增对象类Artisan,头文件Artisan.h及Artisan.m代码如下:

#import <Foundation/Foundation.h>
@class Material;

@interface Artisan : NSObject

- (Material *)makeBanDeng;

@end



#import "Artisan.h"
#import "Material.h"


@implementation Artisan

- (Material *)makeBanDeng{
    return [[Material alloc] initWithDesc:@"Artisan make a bandeng"];
}

@end

 Person.m代码更改如下:

#import "Person.h"
#import "Material.h"
#import "Artisan.h"
@import ObjectiveC;

@implementation Person
- (Material *)makeMaterial{ Material *material = [[Material alloc] initWithDesc:@"green"]; return material; }
- (id)forwardingTargetForSelector:(SEL)aSelector{ NSString *selectorString = NSStringFromSelector(aSelector); if([selectorString compare:@"makeBanDeng"] == NSOrderedSame){ return [[Artisan alloc] init]; } else{ return nil; } } @end

  运行结果如下:

  我们可以发现通过forwardingTargetForSelector:可以重新定位消息接受者,这相当于李四重新找了一个认识的人来做这个事情。这也就是第二种情况。

  如果李四自己不会做同时又找不到会做的人呢?在现实生活中,店长为了不丢掉这个生意,可能会召集全员开会商讨一下如何做这个板凳。在Objective-C中,对应的会启用消息转发机制,收集所有消息的信息然后创建NSInvocation消息对象来处理这件事。NSInvocation包括了selector、target以及参数信息。这个selector名称就是forwardInvation:,这个过程系统成为消息转发。我们有了NSInvocation对象就可以修改这个消息的一切信息,可以修改Target指定某一个人来做,修改selector用其它方法来相应。当然不建议在这里修改Target,修改 Target在上面那种情况修改就可以了,没有必要等到这一步再做。

三、完整的消息转发代码及说明

  现在修改Person.m代码如下:

#import "Person.h"
#import "Material.h"
#import "Artisan.h"
@import ObjectiveC;

@implementation Person

- (Material *)makeMaterial{
    Material *material = [[Material alloc] initWithDesc:@"green"];
    return material;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSString *selectorString = NSStringFromSelector(anInvocation.selector);
    if([selectorString compare:@"makeBanDeng"] == NSOrderedSame){
        [anInvocation setTarget:[[Artisan alloc] init]];
    }else{
        //...什么都不做就会引发异常
    }
}

  修改成如上代码,产生的结果和上一个结果一样。如果到这一步还是没有处理该消息,那么系统将调用doesnotRecognizeSelector方法来抛出异常。到了这里我们是不是想到如果上一步都还没有处理,其实我们还是可以通过重写这个方法来继续处理该消息。

  总结起来就是:

1、李四收到做板凳的消息,发现自己不会做,系统问李四是否需要添加这份技能(resolveInstanceMethod中addMethod),

2、李四自己学不会,但是李四决定找一个认识的人来处理这件事(forwardTargetForSelector:)

3、李四实在是找不到任何人来处理这件事,店长或经理搜集客户需求(selector,methodArgument,处理人target),启用板凳店的终极处理NSInvocation

4、启用之后还是没有什么卵用,店长无赖的告诉张三,doesnotRecognizeSelector

  下面为整个系统流程图:

原文地址:https://www.cnblogs.com/forwk/p/4822052.html