1、简介
copy的目的是产生一个副本,修改源对象不会对副本对象产生影响,修改副本对象不会对源对象产生影响。
iOS提供了两个拷贝方法:
copy,不可变拷贝,产生不可变副本。
mutableCopy,可变拷贝,产生可变副本。
代码举例:
NSString *str1 = [NSString stringWithFormat:@"1"];
NSString *str2 = [str1 copy];
NSMutableString *str3 = [str1 mutableCopy];
str1调用copy得到的str2是不可变字符串,str3是可变字符串。
*可以通过调用appendString:方法检测是否是可变字符串
2、引用计数
当对象调用了copy或者mutableCopy后,引用计数会+1,在MRC环境下当不使用时,需要调用release来释放对象。
NSString *str1 = [NSString stringWithFormat:@"abcdefghijk"];
NSString *str2 = [str1 copy];
NSMutableString *str3 = [str1 mutableCopy];
[str1 release];
[str2 release];
[str3 release];
*Tagged Pointer不需要release
3、深拷贝和浅拷贝
分别打印str1、str2、str3的地址:
NSLog(@"%p %p %p", str1, str2, str3);
打印结果:
~: 0xeef69fc7a6c0e925 0xeef69fc7a6c0e925 0x1005390b0
可以看到str1和str2的内存地址相同,str3是一个新的内存地址,如下图所示:
分析:
str1是不可变字符串,copy的目的是保证源对象和副本互不影响,所以str2指向原来的不可变字符串没有违反copy的目的。
str3是可变字符串,为了保证源对象和副本互不影响,所以需要创建一个新的字符串对象来保存str3。
像str3和str2这样的拷贝方式,被称为深拷贝和浅拷贝:
深拷贝:内容拷贝,产生新的对象。
浅拷贝:指针拷贝,不产生新的对象。
再观察一个例子:
NSMutableString *str1 = [[NSMutableString alloc] initWithFormat:@"abc"];
NSString *str2 = [str1 copy];
NSMutableString *str3 = [str1 mutableCopy];
NSLog(@"%p %p %p", str1, str2, str3);
打印结果:
~: 0x1060348e0 0x46e156054bb85ad9 0x106034e50
可以看到str1、str2、str3分别是三块不同的内存地址,如下图所示:
分析:
str1是可变字符串,调用copy会得到一个不可变字符串,为了保证源对象和副本互不影响,所以需要创建一个新的不可变字符串底线作为副本。
str3调用mutableCopy会得到一个可变字符串,为了保证源对象和副本互不影响,所以需要创建一个新的可变字符串对象作为副本。
不同类型对象调用Copy和MutableCopy结果如下:
总结:不可变对象调用copy是浅拷贝,其他情况是深拷贝。
4、copy修饰属性
比如给Person对象添加一个data属性,用copy修饰:
@interface Person : NSObject
@property (nonatomic, copy) NSArray *data;
@end
在其set方法中会调用copy来进行拷贝对象:
- (void)setData:(NSArray *)data {
if (_data != data) {
[_data release];
_data = [data copy];
}
}
- (void)dealloc {
self.data = nil;
[super dealloc];
}
如果属性是一个NSMutableArray类型的数组,就不能用copy修饰,因为在set方法后,会拷贝出来一个不可变数组,往数组中添加数据时会产生崩溃。
*copy用于修饰不可变对象,strong用于修饰可变对象。
一般NSString对象用copy修饰,因为如果属性是用于显示的话,不希望外部修改字符串导致显示变化。而NSDictionary和NSArray一般用strong修饰。
5、自定义类的Copy
如果Person的实例对象想要调用copy方法,得到一个新的对象,就需要遵守NSCopying协议:
@interface Person : NSObject <NSCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int weight;
@end
并且实现copyWithZone:方法:
- (nonnull id)copyWithZone:(nullable NSZone *)zone {
Person *p = [[Person allocWithZone:zone] init];
p.name = self.name;
p.age = self.age;
p.weight = self.weight;
return p;
}