协议(Protocols)
协议定义了可以由任何类实现的方法。协议至少在以下三种情况下是非常有用的:● 声明希望由别人实现的方法
● 声明对象对外的接口而隐藏自身的类(型)
● 捕获在继承关系图上不相干的类之间的共性
声明由别人实现的接口
类(class)以及类别(category)中声明的都是和某个特定的类相关的方法,主要都是由类自己实现的方法。然而,协议声明的是和具体的类不相干的,可以由任何别的类实现的方法。简单地说协议声明了一个方法列表,不和任何类的定义相关。例如,用于报告用户鼠标动作的方法可以形成一个协议:
-(void)mouseDown:(NSEvent*)theEvent;
-(void)mouseDragged:(NSEvent*)theEvent;
-(void)mouseUp:(NSEvent*)theEvent;
任何相对用户鼠标动作做出反应的类都可以遵循这个协议并实现这些方法。
协议使得方法的声明脱离了类的继承关系,因此它可以以一种与类(class)和(category)不同的方式来使用。协议中声明的方法可能都是在别的地方实现的。实现了这些方法的类不是重点,重点是一个特定类是否遵循了某些协议,也就是说这个类是否实现了协议中定义的那些方法(译者注:也就是说通常我们不关心某个协议都有那些类实现了,而是会关心某个类遵循了那些协议)。这样一来,对象的分类就不仅仅是可以根据他们所属的类在继承关系上的相似性了,还可以根据他们对应的类遵循相同协议的相似性。在继承关系图上位于不同分支的类可能是不相干的,但是从协议的角度来说可能是相似的,因为他们有可能实现了相同的协议。
协议在面向对象的设计中,特别是当一个项目被分割成多个实现部分或者是需要和别的项目中的对象配合工作的时候显得特别重要。Cocoa中的框架大量地使用了协议来通过Objective-C消息支持进程间的通信。
然而Objective-C程序中并不是都要使用协议。和类的定义以及消息表达式不同的是,协议只是提供了一种选择。一些Cocoa框架中使用了协议,一些就没有用到。这完全取决于任务本身的需要。
由别人来实现的方法
如果我们知道对象的类,我们可以通过查看这个类的接口声明知道该对象可以响应那些消息。类的接口声明就表明了其对象可以接收什么样的消息。协议同样也提供了一种表明该类的对象可以接收什么消息的方式。通信是双方的。对象可以发送消息也可以接收消息。例如,一个对象可以委托另外的对象来完成某种特定的操作,也可以只是简单地向别的对象请求所需的信息。在某些情况下,一个对象可能需要通知别的对象自己的状态发生变化了,以便别的对象可以采取必要的措施。
如果消息的接收者和发送者是在同一个项目中(或者是有人已经向你提供了消息接收者及其接口文件),那么这种通信的协调是很容易的。发送者只需要引入接收者的接口文件。接口文件中就声明了发送者在发送消息时使用的方法选择器。
然而,如果我们开发的对象需要发送消息给还没有定义好的对象,也就是由别人来实现的对象,我们就没有接口文件。此时我们就需要别的方式来声明我们发送消息中的还没有实现的方法。协议就可以满足这样的要求。协议告诉了编译器该类中使用的方法,同时也通知别的实现部分他们应该定义这些方法以便他们能和该类协调很好地工作。
假设我们程序中的对象需要通过发送helpOut:消息及其别的消息来请求别的对象的帮助,那么我们的类中可以声明一个assistant实例变量来表示这些消息的接收者,并定义一个伴生的方法来设置该实例变量的值。这个伴生的方法使得别的对象可以注册成为我们程序中消息的接收者:
-setAssistant:anObject
{
assistant = anObject;
}
然后,当我们想要给assistant发送消息的时候,我们可以检查该对象是否可以响应这样的消息:
-(BOOL) doWork
{
...
if([ assistant respondsToSelector:@selector(helpOut:)] )
{
[ assistant helpOut:self];
return YES;
}
return NO;
}
由于在编写上述代码段的时候,我们还不知道什么样的对象会注册成为assistant,我们只能为helpOut:方法声明一个协议。我们不用引入实现该协议的类的接口文件。
为匿名对象声明接口
协议可以被用来声明匿名对象的接口,也就是不知道类型的对象的接口。匿名对象可以代表一种服务或者是一定功能的函数的集合,特别是当程序中只需要该类的一个对象的时候。在应用程序中,必须在使用前进行初始化的、定义了程序架构的重要的类和对象通常都不适合以匿名的方式出现(言外之意,通常只有那些功能有限,并且是在特定场合才使用的非通用类及其对象才比较合适以匿名的方式出现)。对象对于其开发人员来说都不是匿名的。但是当其开发者将其提供给别人的时候,这些对象通常是匿名的。例如,考虑如下的情形:
● 提供给别人使用的框架或者是一系列对象中可以包含不能通过类名或者接口文件而确定类型的对象(即使用者无法知道这些对象的确切类型)。由于不知道类名及其接口文件,使用者就无法创建该类的实例。相反,可以由框架或者函数的提供者提供一个现成的实例。通常都是由另外一个类的方法返回一个可用的这样的匿名对象:
id formatter = [receiver formattingService];
上面方法返回的就是一个没有类类型标识的对象。但是为了保证这个对象是可用的,该方法的提供者至少需要曝露出该对象可以响应哪些消息。此时就可以通过把该对象和其方法关联起来形成一个列表,即协议来实现。
● Objective-C消息是可以被发送给远端对象的(remote objects)----别的程序中的对象。(《Objective-C运行时编程指南》的“远端消息”一节中以更加详细的方式阐述了这种可能性)。
每一个程序中都有自己的结构体,类以及内部的逻辑。但是我们没有必要知道别的程序是如何工作的或者是我们需要和其中的那个部件进行通信。作为外部人员,我们只需要知道可以发送什么消息,给谁发消息就可以了。
当一个应用程序发布其中的一个对象作为远端消息的接收者的时候,还要发布一个协议,其中声明了这个对象可以响应的消息,而不需要声明关于该对象的任何其他信息。发送消息的应用程序也没有必要知道该对象的类型,更不能在自身的设计中使用到这种类型;它所需要知道的就是这个对象遵循的协议。
协议使得匿名对象成为了可能。没有协议,就没有方法来声明对象的接口而不感知该对象的类型。
注意:尽管匿名对象的提供者没有向外界揭示该对象的类型,在运行时该对象是可以自己揭示自身的类型的。一个类消息就可以返回该匿名对象的类。然而,实际中通常很少有必要揭示这种多余的信息;只要知道对象遵循的协议就足够了。