1 copy与mutableCopy方法
- copy方法总是返回对象不可修改的副本,即使该对象本身是可修改的。
- mutableCopy总是返回对象可修改的副本,即使该对象本身是不可修改的。
无论如何,它们返回的总是原对象的副本,当程序对复制的副本进行修改时,原对象通常不会受到影响。
copy示例:
#import <Foundation/Foundation.h>
int main(void) {
@autoreleasepool {
NSMutableString* str = [NSMutableString stringWithString:@"《疯狂iOS讲义》"];
// 复制str不可修改的副本
NSMutableString* strCopy = [str copy];
// 打印副本
NSLog(@"%@", strCopy);
// 因为strCopy是不可修改的,因此下面被注释的代码会报错
// [strCopy appendString:@"aa"];
}
}
运行结果:
2022-05-30 22:51:18.550294+0800 oc.programme[6064:3740118] 《疯狂iOS讲义》
Program ended with exit code: 0
mutableCopy方法示例:
#import <Foundation/Foundation.h>
int main(void) {
@autoreleasepool {
NSMutableString* str = [NSMutableString stringWithString:@"《疯狂iOS讲义》"];
// 复制str可修改的副本
NSMutableString* strCopy = [str mutableCopy];
// 尝试修改副本
[strCopy replaceCharactersInRange:NSMakeRange(3, 3)
withString:@"Android"];
// 修改副本对原字符串无影响
NSLog(@"%@", str);
// 副本已被修改
NSLog(@"%@", strCopy);
}
}
运行结果:
2022-05-31 14:37:39.868271+0800 oc.programme[10398:3885118] 《疯狂iOS讲义》
2022-05-31 14:37:39.869079+0800 oc.programme[10398:3885118] 《疯狂Android讲义》
Program ended with exit code: 0
2 NSCopying与NSMutableCopy协议
虽然NSObject提供了copy和mutableCopy方法,但自定义类并不能直接调用这两个方法来复制自身。
为了保证一个对象可调用copy和mutableCopy方法,通常需要做如下事情。
- 让该类实现NSCopying与NSMutableCopy协议。
- 让该类实现copyWithZone:与mutableCopyWithZone:方法。
当程序调用copy与mutableCopy方法时,程序底层需要调用copyWithZone:与mutableCopyWithZone:方法来完成实际上的复制工作,copy与mutableCopy返回的实际上是copyWithZone:与mutableCopyWithZone:方法的返回值
我们尝试建一个FKDog类,并让其可以调用copy方法复制自己。
该类的接口部分如下:
#import <Foundation/Foundation.h>
@interface FKDog : NSObject
@property (nonatomic, strong) NSMutableString* name;
@property (nonatomic, assign) int age;
@end
该类实现部分如下:
#import "FKDog.h"
@implementation FKDog
- (id)copyWithZone:(NSZone*) zone {
// 使用zone参数创建FKDog对象
FKDog* dog = [[[self class] allocWithZone:zone] init];
dog.name = self.name;
dog.age = self.age;
return dog;
}
@end
上面的程序让FKDog实现了NSCopying协议,并实现了copyWithZone:方法,该方法返回了一个该对象的副本。
测试程序如下:
#import "FKDog.h"
int main(void) {
@autoreleasepool {
FKDog* dog1 = [[FKDog alloc] init];
dog1.name = [NSMutableString stringWithString:@"大黄"];
dog1.age = 12;
// 复制副本
FKDog* dog2 = [dog1 copy];
// 尝试输出副本
NSLog(@"dog2: %@ %d", dog2.name, dog2.age);
// 更改副本(替换name)
dog2.name = [NSMutableString stringWithString:@"dahuang"];
dog2.age = 14;
// 尝试输出
NSLog(@"dog1: %@ %d", dog1.name, dog1.age);
NSLog(@"dog2: %@ %d", dog2.name, dog2.age);
}
}
运行结果:
2022-05-31 15:57:19.862912+0800 oc.programme[11705:3920974] dog2: 大黄 12
2022-05-31 15:57:19.863121+0800 oc.programme[11705:3920974] dog1: 大黄 12
2022-05-31 15:57:19.863143+0800 oc.programme[11705:3920974] dog2: dahuang 14
Program ended with exit code: 0
此处copy方法返回的依然是一个可变的FKDog对象,这是因为FKDog类并没有提供对应的不可变类,自然也就无法复制不可变的FKDog对象。
3 深复制与浅复制
- 浅复制:当对象的实例变量是指针变量时,程序只是复制该地址的指针,而不是真正的复制对象。
- 深复制:不仅复制对象本身,而且会“递归”复制每个指针类型的实例变量。
浅复制
上面的例子就是一个浅复制,我们将测试代码更改为:
#import "FKDog.h"
int main(void) {
@autoreleasepool {
FKDog* dog1 = [[FKDog alloc] init];
dog1.name = [NSMutableString stringWithString:@"大黄"];
dog1.age = 12;
// 复制副本
FKDog* dog2 = [dog1 copy];
// 尝试输出副本
NSLog(@"dog2: %@ %d", dog2.name, dog2.age);
// 更改副本
[dog2.name replaceCharactersInRange:NSMakeRange(0, 2)
withString:@"snoopy"];
dog2.age = 14;
// 尝试输出
NSLog(@"dog1: %@ %d", dog1.name, dog1.age);
NSLog(@"dog2: %@ %d", dog2.name, dog2.age);
}
}
编译运行,我们会发现,dog1的name属性随dog2的name属性变化发生变化:
2022-06-01 18:22:46.611275+0800 oc.programme[13209:3986627] dog2: 大黄 12
2022-06-01 18:22:46.611499+0800 oc.programme[13209:3986627] dog1: snoopy 12
2022-06-01 18:22:46.611519+0800 oc.programme[13209:3986627] dog2: snoopy 14
Program ended with exit code: 0
因为上面的copyWithZone:
方法是直接将dog1的name赋值给dog2的name,但是name只是一个指针变量,该变量储存的只是字符串的地址,这样赋值的结果是dog1的name和dog2的name指向同一个字符串。
深复制
我们对上面的copyWithZone:
方法进行更改:
- (id)copyWithZone:(NSZone*) zone {
// 使用zone参数创建FKDog对象
FKDog* dog = [[[self class] allocWithZone:zone] init];
dog.name = [self.name mutableCopy];
dog.age = self.age;
return dog;
}
上面程序先将原对象的name实例变量复制了一份可变副本,再将可变副本赋值给新对象的name实例变量。这样就保证了原对象与新对象没有任何共用部分,这就实现了深复制。
运行测试代码结果:
2022-06-01 18:42:39.010794+0800 oc.programme[13407:3994508] dog2: 大黄 12
2022-06-01 18:42:39.011213+0800 oc.programme[13407:3994508] dog1: 大黄 12
2022-06-01 18:42:39.011231+0800 oc.programme[13407:3994508] dog2: snoopy 14
Program ended with exit code: 0
一般来说,深复制的实现难度大很多,Foundation框架中的类大部分都只实现了浅复制。
4 setter方法的复制选项
前面介绍合成stter和getter方法可以使用copy,copy指示符指定调用setXxx方法时,程序实际上会使用参数的副本对Xxx实例变量复制。也就是说setXxx:
方法代码如下
- (void) steXxx: (类名*) aXxx {
Xxx = [aXxx copy];
}
copy方法默认是复制该对象的不可变副本,因此就算程序传入的是可变的对象,但程序调用该参数的copy方法得到的是不可变副本。