刚开始使用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
下面为整个系统流程图: