-copy与-mutableCopy都是NSObject类中的方法, 用来拷贝一个对象, 对象所属的类必须遵循NSCopying或者NSMutableCopying协议, 并且实现协议中的方法. -copy返回一个不可变的对象, 无论被拷贝的对象是不可变的(immutable)还是可变的(mutable), 而-mutableCopy则返回一个可变的对象.
读起来挺绕口的, 也很抽象, 这篇文章就结合实际代码来说明-copy和-mutableCopy的工作原理, 分为3部分来讲:
*非集合类对象的拷贝
*集合类对象的拷贝
*让自定义类支持拷贝功能
1.非集合类对象的拷贝(如NSString, NSData)
拷贝的方法有两种: -copy和-mutableCopy, 被拷贝的对象也有两种: 不可变的(immutable)和可变的(mutable), 所以就有4种情况, 下面分别来说明, 拿NSString为例.
(1)不可变对象 和 -copy方法:
NSString *str = @"string";
NSString *cpy = [str copy];
NSLog(@"%p %p", str, cpy);
// 打印结果: 0x10e207088 0x10e207088
可以看到, str和cpy的指针是相同的, 也就是说, 对于不可变对象, -copy方法只进行了指针拷贝(或者说浅拷贝).
(2)可变对象 和 -copy方法:
NSMutableString *str = [NSMutableString stringWithFormat:@"mutable string"];
NSString *cpy = [str copy];
NSLog(@"%p %p", str, cpy);
// 打印结果: 0x600000079b40 0x600000028360
str和cpy的指针不同, 说明对于可变对象, -copy方法重新开辟了一块内存, 进行了内容拷贝(或者说深拷贝). 另外需要注意的是, 虽然str是可变字符串, 但cpy却是不可变的, 因为-copy方法就是拷贝一个不可变的副本.
(3)不可变对象 和 -mutableCopy方法:
NSString *str = @"string";
NSMutableString *cpy = [str mutableCopy];
NSLog(@"%p %p", str, cpy);
// 打印结果: 0x10614f088 0x600000269a40
进行了内容拷贝, 新对象cpy是个可变字符串.
(4)可变对象 和 -mutableCopy方法:
NSMutableString *str = [NSMutableString stringWithFormat:@"mutable string"];
NSMutableString *cpy = [str mutableCopy];
NSLog(@"%p %p", str, cpy);
// 打印结果: 0x60000026a880 0x60000026a8c0
也是内容拷贝.
总结, 对于非集合类对象:
(1)不可变对象: -copy进行指针拷贝, -mutableCopy进行内容拷贝;
(2)可变对象: -copy和-mutableCopy都进行内容拷贝.
2.集合类对象的拷贝(如NSArray, NSDictionary)
依然分4种情况, 这次拿NSArray为例:
(1)不可变对象 和 -copy方法:
NSArray *arr = @[@"1", @"2", @"3"];
NSArray *cpy = [arr copy];
NSLog(@"%p %p", arr, cpy);
// 打印结果: 0x600000241bf0 0x600000241bf0
可以看到, arr和cpy的指针是相同的, 也就是说, 对于不可变数组, -copy方法也只进行了指针拷贝(浅拷贝).
(2)可变对象 和 -copy方法:
NSMutableArray *arr = [NSMutableArray arrayWithObjects:@"1", @"2", @"3", nil];
NSArray *cpy = [arr copy];
NSLog(@"%p %p", arr, cpy);
// 打印结果: 0x600000046e70 0x600000046d80
arr和cpy的指针不同, 说明进行了内容拷贝. 当然需要注意的是, 虽然arr是可变数组, 但cpy却是不可变的. 我们再来打印一下两个数组内所有元素的指针:
for (int i = 0; i < 3; i++) {
NSLog(@"arr[%d]: %p cpy[%d]: %p", i, arr[i], i, cpy[i]);
}
// 打印结果:
// arr[0]: 0x102555088 cpy[0]: 0x102555088
// arr[1]: 0x1025550a8 cpy[1]: 0x1025550a8
// arr[2]: 0x1025550c8 cpy[2]: 0x1025550c8
发现了什么呢? 是的! cpy数组内所有元素的指针与arr数组相同. 也就是说, 虽然数组对象是内容拷贝, 但是数组内的元素却是指针拷贝.
(3)不可变对象 和 -mutableCopy方法:
NSArray *arr = @[@"1", @"2", @"3"];
NSMutableArray *cpy = [arr mutableCopy];
NSLog(@"%p %p", arr, cpy);
// 打印结果: 0x60000004b8e0 0x60000004b340
进行了内容拷贝, 新对象cpy是个可变数组. 再次打印两个数组内所有元素的指针, 会发现各个元素依然是指针拷贝.
(4)可变对象 和 -mutableCopy方法:
NSMutableArray *arr = [NSMutableArray arrayWithObjects:@"1", @"2", @"3", nil];
NSMutableArray *cpy = [arr mutableCopy];
NSLog(@"%p %p", arr, cpy);
// 打印结果: 0x600000047e30 0x600000047a10
结果同(3).
总结, 对于集合类对象:
(1)不可变对象: -copy进行指针拷贝, -mutableCopy进行内容拷贝, 而数组内的元素进行指针拷贝;
(2)可变对象: -copy和-mutableCopy都进行内容拷贝, 而数组内的元素进行指针拷贝.
3.让自定义类支持拷贝功能
其实很简单, 只需两步:
1.遵循NSCopying协议;
2.实现协议中的-copyWithZone:方法.
至于NSMutableCopying协议和-mutableCopyWithZone:方法, 本文就不做讨论了.
定义一个类, 遵循NSCopying协议:
@interface XYZPerson : NSObject <NSCopying>
@property (nonatomic, copy) NSString *name; // 姓名
@property (nonatomic, copy) NSString *number; // 手机号
@end
实现-copyWithZone:方法:
@implementation XYZPerson
- (NSString *)description {
return [NSString stringWithFormat:@"address: %p, name: %@, number: %@", self, self.name, self.number];
}
- (id)copyWithZone:(NSZone *)zone {
XYZPerson *cpy = [[[self class] allocWithZone:zone] init];
cpy.name = self.name;
cpy.number = self.number;
return cpy;
}
@end
至此, 一个最简单的拷贝功能就完成了, 验证一下:
XYZPerson *p1 = [[XYZPerson alloc] init];
p1.name = @"Jack";
p1.number = @"12345678";
NSLog(@"%@", p1);
XYZPerson *p2 = [p1 copy];
NSLog(@"%@", p2);
p1.name = @"Rose";
p1.number = @"23456789";
NSLog(@"%@", p1);
NSLog(@"%@", p2);
// 打印结果:
// address: 0x60800003c100, name: Jack, number: 12345678
// address: 0x60800003bf20, name: Jack, number: 12345678
// address: 0x60800003c100, name: Rose, number: 23456789
// address: 0x60800003bf20, name: Jack, number: 12345678
可以看到, -copy方法重新开辟了一块内存来存放p2对象, 它和p1是互相独立的, 后期修改p1的属性值, 并不会影响到p2, 反之亦然.