一、copy与mutableCopy
一个对象如果使用copy和mutableCopy可以创建对象的副本,copy创建的是不可变副本(如:NSString,NSArray),而mutableCopy创建的是可变副本(如:NSMutableString,NSMutableArray)。
二、浅拷贝与深拷贝
(1)浅拷贝
浅拷贝相当于指针拷贝,也就是在原来对象上做一次retain操作,源对象和副本指向的是同一个对象,源对象的引用计数会+1。
只有不可变的对象创建的不可变副本(copy)才是浅拷贝,其他都是深拷贝。
例如:
NSString *str = @"copy不可变字符串";
NSString *str1 = [str copy];
NSLog(@"string1 = %@ str1 = %@",str,str1);
NSLog(@"string1 = %p str1 = %p",str,str1);
该代码打印出来的结果是
可见copy之后,str和str1两个指针指向的是同一个对象
(2)深拷贝
深拷贝可以实现真正意义上的拷贝,他不仅拷贝指针,而且还拷贝内容,源对象和副本指向的不再是同一个对象,源对象的引用计数不变,副本的引用计数置为1。
(3)使用mutableCopy可以实现深拷贝
例如:
NSString *str = @"mutableCopy不可变字符串的";
NSMutableString *str1 = [str mutableCopy];
NSLog(@"str = %@ str1 = %@",str,str1);
NSLog(@"str = %p str1 = %p",str,str1);
该代码执行的结果如下
可见mutableCopy之后副本与源对象指针指向的是不同的对象,但是数据结构(内容)还是一样的。
如果对str1进行修改
NSString *str = @"mutableCopy不可变字符串的";
NSMutableString *str1 = [str mutableCopy];
[str1 appendString:@"-修改了"];
NSLog(@"str = %@ str1 = %@",str,str1);
NSLog(@"str = %p str1 = %p",str,str1);
执行结果如下
由此可见mutableCopy出来的是两个不同的对象。不过当我们用NSString *str1 来接收[str mutableCopy]时,得到的将是一个不可变的副本。
(4)对可变的字符串执行copy
该操作也将会是深拷贝
NSMutableString *str = [NSMutableString stringWithFormat:@"copy可变字符串"];
NSString *str1 = [str copy];
NSLog(@"str = %@ str1 = %@",str,str1);
NSLog(@"str = %p str1 = %p",str,str1);
程序执行结果如下:
可见这时候的copy也是一个深拷贝
如果我们将接收可变对象copy出来的副本的指针该为可变类型的,例如
NSMutableString *str = [NSMutableString stringWithFormat:@"copy可变字符串"];
NSMutableString *str1 = [str copy];
[str1 appendString:@"111"];
NSLog(@"str = %@ str1 = %@",str,str1);
NSLog(@"str = %p str1 = %p",str,str1);
那么此时系统将会报一个严重的错误“
Attempt to mutate immutable object with appendString”尝试用appendString方法去改变不可变的对象。因此,当copy一个可变对象时会返回一个不可变的副本,OC这样做是为了能在可变版本与不可变版本之间自由切换。
(5)对可变字符串进行mutableCopy
该操作也会是深拷贝:
NSMutableString *str = [NSMutableString stringWithFormat:@"mutableCopy可变字符串"];
NSMutableString *str1 = [str mutableCopy];
NSLog(@"str = %@ str1 = %@",str,str1);
NSLog(@"str = %p str1 = %p",str,str1);
代码执行结果如下
此时接收的是一个可变副本,如果用NSString *str1,那接收的将会是一个不可变的副本
三、让类具备拷贝能力
如果想让一个类具有拷贝功能,那么就要实现NSCopying协议,该协议中就只有一个方法:
- (id)copyWithZone : (NSZone *)zone
NSZone是用来分配和释放内存的一种方式,它不是一个对象,而是用c结构存储了关于对象的内存管理信息。copy方法由NSObject实现,该方法只是以zone为参数来调用“copyWithZone”方法,所以重写copy方法其实真正需要实现的却是“copyWithZone:”方法,若想使某个类具有拷贝功能,只需声明该类遵从NSCopuing协议,并实现该方法即可
例如有个Person类
#import "Person.h"
@interface Person : NSObject <NSCopying>
@property (nonatomic,copy)NSString *firstName;
@property (nonatomic,copy)NSString *lastName;
@end
类的实现文件中重写了copyWithZone方法,使得该类具备了拷贝功能#import "Person.h"
@implementation Person
- (id)copyWithZone:(NSZone *)zone
{
Person *copy = [[[self class] allocWithZone:zone]init];
copy.firstName = _firstName ;
copy.lastName = _lastName;
return copy;
}
@end
在main函数中测试该类
#import <Foundation/Foundation.h>
#import "Person.h"
int main()
{
Person *p = [[Person alloc] init];
p.firstName = @"cx";
p.lastName = @"wj";
NSLog(@"%@",p.firstName);
// 打印对象p以及属性值的地址
NSLog(@"%@--%p",p,p.firstName);
// 拷贝对象p
Person *p2 = [p copy];
NSLog(@"%@--%p",p2,p2.firstName);
return 0;
}
执行结果如下:
虽然指针不同,但却是同一对象,可见是浅拷贝
Person.m改为
- (id)copyWithZone:(NSZone *)zone
{
Person *copy = [[[self class] allocWithZone:zone]init];
copy.firstName = [_firstName copy] ;
copy.lastName = [_lastName mutableCopy];
return copy;
}
@end
main.m改为
#import <Foundation/Foundation.h>
#import "Person.h"
int main()
{
Person *p = [[Person alloc] init];
p.firstName = @"cx";
p.lastName = @"wj";
NSLog(@"%@",p.firstName);
// 打印对象p以及属性值的地址
NSLog(@"%@--firstName-address=%p,lastName-address=%p",p,p.firstName,p.lastName);
// 拷贝对象p
Person *p2 = [p copy];
NSLog(@"%@--firstName-address=%p,lastName-address=%p",p2,p2.firstName,p2.lastName);
return 0;
}
可见lastName是深拷贝。
四、总结
(1)深拷贝会产生新对象而浅拷贝不会
(2)不可变对象的copy是才是浅拷贝,其他情况都是深拷贝
(3)若想让类具备拷贝功能就必须遵守NSCopy协议并重写copyWithZone方法
(4)一般情况下尽量使用浅拷贝,深拷贝会使内存中有两个一模一样的对象,会造成浪费
(5)NSString一般使用copy策略,copy代表set方法会release旧对象,copy新对象,这样可以保护属性的封装性,因为传递给set方法的新值有可能指向一个NSMutableString类的实例,这个类是NSString的子类,表示的是一种可以修改的字符串,此时若是不拷贝字符串,那么设置完属性之后字符串的值就有可能在对象不知情的情况下被更改,所以这时需要拷贝一份不可变的字符串,确保对象中的字符串值不会被修改。只要实现对象的属性的值是可变的,就应该在set时拷贝一份。