iOS中的深拷贝和浅拷贝

前言

  • 比喻的观点,浅拷贝是对文件创建了一个快捷方式,实质上这个快捷方式指向的还是原来的文件。深拷贝是对文件进行真正的复制粘贴,形成了一个独立的新文件。

  • 指向内存地址的观点,浅拷贝的对象指向的是还是被拷贝对象的内存地址,深拷贝的对象指向一个新的内存地址。

一. copy 与 mutableCopy 方法

要注意的是,我们并不能单纯地把copymutableCopy认为是浅拷贝深拷贝

  • 对于copy来说,被拷贝对象为不可变对象浅拷贝可变对象深拷贝
  • 对于mutableCopy来说,都是深拷贝
  • 对于copy来说,不论被拷贝对象是否可变,拷贝后的对象都是不可变的
  • 对于mutableCopy 来说,不论被拷贝对象是否可变,拷贝后的对象都是可变的

iOS 对象大致可以分为容器对象非容器对象,细分下去,可以分为 可变容器对象(NSMutableArray),不可变容器对象(NSArray) ,可变非容器对象(NSMutableString,NSMutableDictionary), 不可变非容器对象(NSString,NSDictionary)

下面我们来实际验证一下copy与mutableCopy的效果

  1. 非容器不可变对象
  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深拷贝

  1. 非容器可变对象
 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关键字修饰的问题-数据安全问题。详细见下面。

  1. 容器不可变对象
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`都是`浅拷贝` 
复制代码
  1. 容器可变对象
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.对于容器内的元素来说,copymutableCopy都是浅拷贝

  1. 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之后得到的对象都不可变

  1. 自定义对象
  • 对于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关键字修饰的区别

  • 对于不可变字符串,copystrong关键字修饰并无差别
  • 对于可变字符串:
    1. copy修饰的属性赋值时会进行一次copy操作(对于可变字符串的拷贝是深拷贝),也就是为它开辟了新的内存地址。
    2. 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.strongStrself.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类型的属性。

猜你喜欢

转载自juejin.im/post/7055644634894057479