iOS 中的深拷贝与浅拷贝
前言
-
用
比喻
的观点,浅拷贝
是对文件创建了一个快捷方式
,实质上这个快捷方式
指向的还是原来的文件。深拷贝
是对文件进行真正的复制粘贴
,形成了一个独立的新文件。 -
用
指向内存地址
的观点,浅拷贝
的对象指向的是还是被拷贝对象的内存地址,深拷贝
的对象指向一个新的内存地址。
一. copy 与 mutableCopy 方法
要注意的是,我们并不能单纯地把copy
和mutableCopy
认为是浅拷贝
和深拷贝
。
- 对于
copy
来说,被拷贝对象为不可变对象
是浅拷贝
,可变对象
是深拷贝
- 对于
mutableCopy
来说,都是深拷贝
- 对于
copy
来说,不论被拷贝对象是否可变,拷贝后的对象都是不可变的 - 对于
mutableCopy
来说,不论被拷贝对象是否可变,拷贝后的对象都是可变的
iOS 对象大致可以分为容器对象
和非容器对象
,细分下去,可以分为 可变容器对象(NSMutableArray)
,不可变容器对象(NSArray)
,可变非容器对象(NSMutableString,NSMutableDictionary)
,不可变非容器对象(NSString,NSDictionary)
。
下面我们来实际验证一下copy与mutableCopy的效果
- 非容器不可变对象
NSString *str1 = @"非容器不可变对象";
NSString *str2 = [str1 copy];
NSString *str3 = [str1 mutableCopy];
NSLog(@"str1:%p class:%@",str1,[str1 class]);
NSLog(@"str2:%p class:%@",str2,[str2 class]);
NSLog(@"str3:%p class:%@",str3,[str3 class]);
// 打印结果
str1:0x105718738 class:__NSCFConstantString
str2:0x105718738 class:__NSCFConstantString
str3:0x60400024eb80 class:__NSCFString
结论:对于
非容器不可变对象
来说,copy
是浅拷贝
,mutableCopy
是深拷贝
- 非容器可变对象
NSMutableString *str1 = [NSMutableString stringWithFormat:@"非容器可变对象"];
NSMutableString *str2 = [str1 copy];
NSMutableString *str3 = [str1 mutableCopy];
NSLog(@"str1:%p class:%@",str1,[str1 class]);
NSLog(@"str2:%p class:%@",str2,[str2 class]);
NSLog(@"str3:%p class:%@",str3,[str3 class]);
// 打印结果
str1:0x600000251be0 class:__NSCFString
str2:0x600000251010 class:__NSCFString
str3:0x600000251c40 class:__NSCFString
结论:对于
非容器可变对象
来说,copy
,mutableCopy
都是深拷贝
这个结论其实解释了NSString
对象建议用copy
而不是strong
关键字修饰的问题-数据安全问题。详细见下面。
- 容器不可变对象
NSArray *array1 = [NSArray arrayWithObjects:@"非容器不可变对象",[NSMutableString stringWithFormat:@"非容器可变对象"], nil];
NSArray *array2 = [array1 copy];
NSArray *array3 = [array1 mutableCopy];
NSLog(@"array:%p copyArray:%p mutableCopyArray:%p",array1,array2,array3);
NSLog(@"array1[0]:%p array1[1]:%p ",array1[0] , array1[1]);
NSLog(@"array2[0]:%p array2[1]:%p ",array2[0] , array2[1]);
NSLog(@"array3[0]:%p array3[1]:%p ",array3[0] , array3[1]);
// 打印结果
array:0x6040002312a0 copyArray:0x6040002312a0 mutableCopyArray:0x6040002544f0
array1[0]:0x105718738 array1[1]:0x60400024eb80
array2[0]:0x105718738 array2[1]:0x60400024eb80
array3[0]:0x105718738 array3[1]:0x60400024eb80
结论: 1. 对于
容器不可变对象
来说,copy
是浅拷贝
,mutableCopy
是深拷贝
2. 对于容器内的元素来说,copy
和mutableCopy
都是浅拷贝
- 容器可变对象
NSMutableArray *array1 = [NSMutableArray arrayWithObjects:@"非容器不可变对象",[NSMutableString stringWithFormat:@"非容器可变对象"], nil];
NSMutableArray *array2 = [array1 copy];
NSMutableArray *array3 = [array1 mutableCopy];
NSLog(@"array1:%p array2:%p array3:%p",array1,array2,array3);
NSLog(@"array1[0]:%p array1[1]:%p ",array1[0], array1[1]);
NSLog(@"array2[0]:%p array2[1]:%p ",array2[0], array2[1]);
NSLog(@"array3[0]:%p array3[1]:%p ",array3[0], array3[1]);
// 打印结果
array1:0x604000254820 array2:0x604000231a00 array3:0x6040002545b0
array1[0]:0x105718738 array1[1]:0x6040002544f0
array2[0]:0x105718738 array2[1]:0x6040002544f0
array3[0]:0x105718738 array3[1]:0x6040002544f0
结论:1. 对于
容器可变对象
来说,copy
,mutableCopy
都是深拷贝
2.对于容器内的元素来说,copy
和mutableCopy
都是浅拷贝
- copy 与mutableCopy 后得到的对象
NSString *str1 = @"非容器不可变对象";
NSMutableString *str2 = [str1 mutableCopy];
[str2 appendString:@"111"];
NSLog(@"str2:%@",str2);
// 打印结果
str2:非容器不可变对象111
/******************************************************************************/
NSMutableString *str1 = [NSMutableString stringWithFormat:@"非容器可变对象"];
NSMutableString *str2 = [str1 copy];
[str2 appendString:@"111"];
// 执行[str2 appendString:@"111"]报错
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with appendString:'
结论:1. 不论被拷贝对象是否可变,执行了
mutableCopy
之后得到的对象都是可变的
2.不论被拷贝对象是否可变,执行了copy
之后得到的对象都不可变
- 自定义对象
- 对于
copy
,自定义类需要添加NSCopying
协议,并实现对应的copyWithZone:
方法。 - 对于
mutableCopy
,自定义类需要添加NSMutableCopying
协议,并实现对应的mutableCopyWithZone:
方法。
@interface Singer() <NSCopying>
@property (nonatomic, strong) NSString *name;
@end
- (id)copyWithZone:(NSZone *)zone {
Singer *singer = [[Singer allocWithZone:zone] init];
singer.name = self.name;
return singer;
}
二. NSString 对象copy和strong关键字修饰的区别
- 对于不可变字符串,
copy
和strong
关键字修饰并无差别 - 对于可变字符串:
-
copy
修饰的属性赋值时会进行一次copy
操作(对于可变字符串
的拷贝是深拷贝
),也就是为它开辟了新的内存地址。 -
strong
修饰的属性则不会进行深拷贝
操作,不会开辟新的内存地址。
-
NSString *str = @"我是个不可变字符串";
self.strongStr = str;
self.copyedStr = str;
NSLog(@"str:%p, strongStr:%p, copyedStr:%p",str,self.strongStr,self.copyedStr);
// 打印结果
str:0x10cd90978, strongStr:0x10cd90978, copyedStr:0x10cd90978
当字符串是不可变类型时,
self.strongStr
和self.copyedStr
并无区别,指向的都是str
的内存地址。
NSMutableString *str = [NSMutableString stringWithFormat:@"我是个可变字符串"];
self.strongStr = str;
self.copyedStr = str;
[str appendString:@"哈哈哈"];
NSLog(@"str:%p, strongStr:%p, copyedStr:%p",str,self.strongStr,self.copyedStr);
NSLog(@"str:%@, strongStr:%@, copyedStr:%@",str,self.strongStr,self.copyedStr);
// 打印结果
str:0x6000002507d0, strongStr:0x6000002507d0, copyedStr:0x600000251760
str:我是个可变字符串哈哈哈, strongStr:我是个可变字符串哈哈哈, copyedStr:我是个可变字符串
当字符串是可变类型时,
self.copyedStr
指向了新的内存地址,self.copyedStr
指向的依旧是str
的内存地址。
用strong
关键字修饰NSString
类型的属性究竟会造成什么问题呢?
当我们对strongStr
属性赋值完毕之后,对str
进行修改,由于指向的是同一个内存地址,self. strongStr
的值也发生了变化。strongStr
在我们不知道的情况下发生了修改行为,这是不安全的。所以在类型不明确的情况下,一般建议使用copy
关键字修饰NSString
类型的属性。