http://www.ituring.com.cn/article/213863
O博士:接下来,我们继续在α科研中心了解作战单位的制造过程。本章讨论的主题是在实战过程中积累的制造方法,主要的目的就是对制造的基本作战单位做出统一的标准,这样可以方便制造、维护、改造等工作。
在Objective-C中,协议(protocol)用于定义一系列标准(如各种方法,实际上在协议中也只能定义方法^^),然后由具体的类来实现;而且,一个协议可以由多个类来实现,在代码中,我们就可以使用相同的方法来操作这些类和对象。
本章,我们就来讨论协议的相关内容,主要包括:
- 创建协议
- 实现协议
- 可选成员
- 实现多个协议
- 对象复制(实现NSCopying协议)
5.1. 创建协议
O博士:在制造作战单位的过程中,我们使用协议定义一系列的标准,但协议本身并不会做任何的实际工作;在代码中,我们一般会将协议定义在独立的头文件中,并使用@protocol指令声明一个协议。
下面的代码,我们将定义一个PRobotUnit协议,其中定义了一个move方法和fire方法(PRobotUnit.h文件)。
#ifndef __PRobotUnit_h__
#define __PRobotUnit_h__
@protocol PRobotUnit
-(void) move;
-(void) fire;
@end
#endif
O博士:我们可以看到,协议很像类的接口部分,是的,只不过,协议只限于声明成员,而接下来,我们将使用类来实现这个协议。
5.2. 实现协议
O博士:为了模块化生产各种机器人战士,我们会首先制造一个基本型的机器人,首先是类的接口部分,如下面的代码(CRobotUnit.h文件)。
#ifndef __CRobotUnit_h__
#define __CRobotUnit_h__
#import <Foundation/Foundation.h>
#import "PRobotUnit.h"
@interface CRobotUnit : NSObject <PRobotUnit>
@end
#endif
请注意,在CRobotUnit类中,我们并没有声明任何成员,但我们在类的声明后面使用一对尖括号<>包含了所遵循的协议,所以,类中应该实现的成员也就已经不言自明了。接下来,我们在CRobotUnit类的实现代码中就需要定义move和fire方法,如下面的代码(CRobotUnit.m)。
#import "CRobotUnit.h"
@implementation CRobotUnit
-(void) move
{
NSLog(@"机器人移动中");
}
-(void) fire
{
NSLog(@"机器人开火中");
}
@end
下面的代码,我们将测试CRobotUnit类的使用。
#import <Foundation/Foundation.h>
#import "CRobotUnit.h"
int main(int argc, char *argv[])
{
@autoreleasepool {
CRobotUnit *robot5 = [[CRobotUnit alloc] init];
[robot5 move];
[robot5 fire];
}
}
O博士:当我们检测机器人的状态时,就可以确认对象所属的类型是否实现了某个协议,此时,我们可以使用conformsToProtocol:方法,其定义如下:
-(BOOL) conformsToProtocol:protocol;
方法中的参数protocol可以使用@protocol()指令获取。如下面的代码,我们将测试robot5对象是否实现了PRobotUnit协议。
CRobotUnit *robot5 = [[CRobotUnit alloc] init];
BOOL result = [robot5 conformsToProtocol: @protocol(PRobotUnit)];
5.3. 可选成员
O博士:有些时候,如一个维修机器人,这是不需要开火的,所以,我们可以将fire方法定义为可选成员,也就是说,在实现协议的类中,并不要求一定要实现它。
在声明协议时,可以使用@optional指令,在此指令后的成员即声明为协议的可选成员。如下面的代码,我们将在PRobotUnit协议中的fire方法定义为可选成员(PRobotUnit.h文件)。
#ifndef __PRobotUnit_h__
#define __PRobotUnit_h__
@protocol PRobotUnit
-(void) move;
@optional
-(void) fire;
@end
#endif
O博士:然后,在实现PRobotUnit协议的类中,我们可以实现这个方法,也可以选择不实现它。如果我们不能确定一个对象中是否可以使用这个方法,可以respondsToSelector:实例方法进行判断。如下面的代码。
CRobotUnit *robot6 = [[CRobotUnit alloc] init];
BOOL result = [robot6 respondsToSelector:@selector(fire)];
if (result) {
[robot6 fire];
}
我们也可以简化一下代码,如:
CRobotUnit *robot6 = [[CRobotUnit alloc] init];
if ([robot6 respondsToSelector:@selector(fire)]) [robot6 fire];
大家可以自己修改CRobotUnit类的代码,然后观察实现和不实现fire方法时的运行结果有什么不同。
5.4. 实现多个协议
O博士:前面,我们创建的机器人协议只具有基本功能,在大规模生产以后,我们需要知道它们的型号和编号;对于这些内容,我们可以再定义一个协议,如下面的代码(PRobotIdentifier.h)。
#ifndef __PRobotIdentifier_h__
#define __PRobotIdentifier_h__
@protocol PRobotIdentifier
-(NSString*) model;
-(void) setModel:(NSString*)m;
-(int) identifier;
-(void) setIdentifier:(int)ident;
@end
#endif
实际上,大家可以看到,我们在使用setter和getter方法给机器人协议添加两个属性,即model和identifier属性。
然后,我们可以让CRobotUnit类同时实现多个协议,如同时实现PRobotUnit和PRobotIdentifier两个协议,此时,我们需要使用逗号分隔所需要实现的协议。如下面的代码:
#ifndef __CRobotUnit_h__
#define __CRobotUnit_h__
#import <Foundation/Foundation.h>
#import "PRobotUnit.h"
#import "PRobotIdentifier.h"
@interface CRobotUnit : NSObject <PRobotUnit,PRobotIdentifier>
@end
#endif
接下来,在CRobotUnit类的实现中,我们必须实现PRobotUnit协议和PRobotIdentifier协议中的所有成员;当然,也可以不实现可选成员,谁让它们是可选的呢?^^
下面就是完整的CRobotUnit类的代码。
#import "CRobotUnit.h"
@implementation CRobotUnit
{
NSString* _model;
int _identifier;
}
-(void) move
{
NSLog(@"%i 号机器人移动中", _identifier);
}
-(void) fire
{
NSLog(@"%i 号机器人开火中", _identifier);
}
-(NSString*) model
{
return _model;
}
-(void) setModel:(NSString*)m
{
_model = m;
}
-(int) identifier
{
return _identifier;
}
-(void) setIdentifier:(int)ident
{
_identifier = ident;
}
@end
5.5. 对象复制(实现NSCopying协议)
O博士:前一章,我们讨论过,通过赋值运算符(=),对象传递的只是引用(指针),如果我们需要得到一个对象真正的副本,就需要做一些深复制工作。在Foundation资源中,为我们提供了一个内部机制,即实现NSCopying协议,通过实现这个协议,我们就可以调用对象的copy方法复制一个全新的对象副本。
接下来,我们将修改CRobotUnit类,使其实现NSCopying协议,其接口部分的代码修改如下(CRobotUnit.h文件)。
#ifndef __CRobotUnit_h__
#define __CRobotUnit_h__
#import <Foundation/Foundation.h>
@interface CRobotUnit : NSObject <PRobotUnit, PRobotIdentifier, NSCopying>
@end
#endif
然后,在CRobotUnit类的实现部分,我们会实现NSCopying协议中的copyWithZone:方法,它的定义如下:
-(id) copyWithZone:(NSZone*) zone;
下面就是CRobotUnit.m文件中添加的copyWithZone:方法的实现:
-(id) copyWithZone:(NSZone*) zone
{
CRobotUnit *robot = [[CRobotUnit alloc] init];
robot.model = [self.model copy];
robot.identifier = self.identifier;;
return robot;
}
在copyWithZone:方法的参数中,NSZone类型是一个结构类型,其功能是管理内存区域;不过,由于我们已经开始使用ARC,在应用中的内存管理和工作几乎不需要我们干预,所以,这个参数实际上在很多情况下是不需要使用的。另外还有一个与NSZone类型相关的方法就是NSObject类中定义的allocWihtZone:方法。
下面的代码,我们在主函数中测试CRobotUnit对象的复制操作。
CRobotUnit *robot5 = [[CRobotUnit alloc] init];
robot5.model = @"KILLER-I";
robot5.identifier = 110099;
CRobotUnit *robot6 = [robot5 copy];
NSLog(@"robot5 , model = %@ , identifier = %i", robot5.model, robot5.identifier);
NSLog(@"robot6 , model = %@ , identifier = %i", robot6.model, robot6.identifier);
robot6.model = @"SuperSolider - II";
robot6.identifier = 990011;
NSLog(@"robot5 , model = %@ , identifier = %i", robot5.model, robot5.identifier);
NSLog(@"robot6 , model = %@ , identifier = %i", robot6.model, robot6.identifier);
执行此代码,观察运行结果;如果想加深对于对象的复制问题,可以将代码“CRobotUnit *robot6 = [robot5 copy];”修改为“CRobotUnit *robot6 = robot5;”,然后,再执行代码,观察两次执行的结果有什么不同。实际上,这就是对象深复制和浅复制的区别。
O博士:利用NSCopying协议实现对象的深复制时,有一点需要注意,当成员的类型是对象时,我们需要在copyWithZone:方法中对它的成员也进行逐一复制,这样一来,事情似乎有些复杂了,不过情况就是这样。如CRobotUnit类中的model属性,它就定义为NSString类型,我们在复制其内容时,就使用了NSString类中的copy来完成深度复制。
此外,我们还可以使用归档来完成对象复制工作,在第9章,我们会看到相关的讨论。