协议就是定义了一组方法,然后要求其他类去实现。
以下类的复制来举例说明,遵守NSCopying协议的类是如何实现复制的。
0x01 NSCopying协议
NSCopying是对象拷贝的协议。
类的对象如果支持拷贝,则该类应遵守并实现NSCopying协议。
用NSCopying协议来举例,是因为该协议中的方法只有一个,比较容易理解:
- (id)copyWithZone:(NSZone *)zone {
Person *model = [[[self class] allocWithZone:zone] init];
model.firstName = self.firstName;
model.lastName = self.lastName;
//未公开的成员
model->_nickName = _nickName;
return model;
}
0x02 复制Engine类
为了能够复制engine对象,Engine类需要采用NSCopying协议。
所以Engine类的接口更新如下:
//Engine类的接口
@interface Engine : NSObject <NSCopying>
...
@end // Engine
因为Engine类采用了NSCopying协议,所以必须实现协议中的copyWithZone:方法。
zone是NSZone类的一个对象,指向一块可供分配的内存区域。
当你向一个对象发送copy消息时,该copy消息实际上将会转换成copyWithZone:方法。
Engine类的copyWithZone:方法实现如下:
//Engine类的实现
@implementation Engine
- (id) copyWithZone: (NSZone *) zone
{
Engine *engineCopy;
engineCopy = [[[self class] allocWithZone: zone] init];
return (engineCopy);
} // copyWithZone
...
copyWithZone
copyWithZone:方法首先获得self参数所属的类,然后向self对象所属的类发送allocWithZone:消息以分配内存并创建一个该类的新对象。最后,copyWithZone:方法给这个对象发送init:消息使其初始化。
这里也可以使用指定初始化函数来执行初始化工作。
allocWithZone
alloc是类方法,allocWithZone也是类方法。
allocWithZone:方法的最后一行会返回新创建的对象。
+ (id) allocWithZone: (NSZone *) zone;
为什么这里使用[[self class] allocWithZone: zone]而不是[Engine allocWithZone: zone]?
假如Engine类有个子类Slant6。
如果我们直接给Engine类发送allocWithZone:消息,创建的将是一个新的Engine类对象,而不是Slant6类对象。
通过使用[self class],allocWithZone:消息会被正确地发送给正在接收copy消息的对象所属的类。
0x03 复制Tire系列类
复制超类Tire
Tire类的复制工作比Engine类复杂些,因为Tire类有两个实例变量,其子类AllWeatherRadial又引入了两个实例变量。
既然是复制,那么实例变量也是必须被复制的。
Tire类采用协议的新接口代码如下:
//Tire类的接口
@interface Tire : NSObject <NSCopying>
@property float pressure;
@property float treadDepth;
...
@end // Tire
Tire类的copyWithZone:方法实现如下:
//Tire类的实现
- (id) copyWithZone: (NSZone *) zone
{
Tire *tireCopy;
tireCopy = [[[self class] allocWithZone: zone] initWithPressure:pressure
treadDepth:treadDepth];
return (tireCopy);
} // copyWithZone
...
这里因为Tire类需要复制两个实例变量,所以使用指定初始化函数来完成初始化工作。
复制子类AllWeatherRadial
子类AllWeatherRadial的接口代码不需要变化:
//AllWeatherRadial类的接口
@interface AllWeatherRadial : Tire
// ... properties
// ... methods
@end // AllWeatherRadial
为什么子类的接口不用写<NSCopying>协议名?
因为子类继承超类的时候,也包括获得超类的所有属性,遵守超类遵守的协议。
但是必须重写copyWithZone:方法,因为AllWeatherRadial类中有两个实例变量是新增的,必须同时被复制:
//子类AllWeatherRadial的实现
- (id) copyWithZone: (NSZone *) zone
{
AllWeatherRadial *tireCopy;
tireCopy = [super copyWithZone: zone];
//复制子类新增的实例变量
tireCopy.rainHandling = rainHandling;
tireCopy.snowHandling = snowHandling;
return (tireCopy);
} // copyWithZone
...
为什么这里用super而不用self?
因为AllWeatherRadial类继承自Tire类,所以子类只需直接请求超类执行copy操作,并期望超类正确地复制以及在分配对象时使用[self class]。所以,这里用super直接访问超类。
关于self和super的用法,可以参考另一篇笔记:《Objective-C中的self和super详解》
剩下的工作就是把rainHandling和snowHandling两个实例变量复制到新的对象tirecopy中去。
0x04 复制Car类
Car类没有什么特殊的,一样要遵守NSCopying协议。接口代码修改如下:
//Car类的接口
@interface Car : NSObject <NSCopying>
// properties
// ... methods
@end // Car
修改后的Car类实现如下:
//Car类的实现
- (id) copyWithZone: (NSZone *) zone
{
Car *carCopy;
carCopy = [[[self class] allocWithZone: zone] init];
//复制车名
carCopy.name = self.name;
//复制Engine
Engine *engineCopy;
engineCopy = [engine copy];
carCopy.engine = engineCopy;
//复制Tire
for (int i = 0; i < 4; i++)
{
Tire *tireCopy;
tireCopy = [[self tireAtIndex: i] copy];
[carCopy setTire: tireCopy atIndex: i];
}
return (carCopy);
} // copyWithZone
...
为什么这里并没有使用NSArray代替for循环?
因为NSArray的copy方法只会创建一个浅复制(shalldow copy)而不是深复制(deep copy)。
关于浅复制和深复制(deep copy)见另一篇笔记分析。