详解深浅拷贝

详解深浅拷贝前言说道拷贝大家可能联想到深拷贝和浅拷贝概念 然而在 swift 却很少涉及宝贝问题 即使 swift 下依然有 copy 和 mutableCopy 方法 但终其原因 创建单纯的 swift 类并不需要继承 NSObject 而是使用 swift 类 另外很多牵扯拷贝问题的数组和字典 在 swift 对应于 Array 和 Dictionary 不同于 NSArray 和 NSDictionary swift 的 Array 和 Dictionary 是值类型 赋值后并不需要担心其拷贝问题 所以主要结束 Objective C 中的深拷贝和浅拷贝

前言

说道拷贝大家可能联想到深拷贝和浅拷贝概念,然而在swift却很少涉及宝贝问题,即使swift下依然有copy和mutableCopy方法。但终其原因,创建单纯的swift类并不需要继承NSObject,而是使用swift类,另外很多牵扯拷贝问题的数组和字典,在swift对应于Array和Dictionary,不同于NSArray 和NSDictionary,swift的Array和Dictionary是值类型,赋值后并不需要担心其拷贝问题。

所以主要Objective-C中的深拷贝和浅拷贝。

什么是深拷贝?什么是浅拷贝???

深拷贝是深度拷贝,是拷贝一个实例对象到一个新的内存地址,

浅拷贝只是简单拷贝一个实例对象的指针。

从图中可以看到,集合的

浅拷贝后的数组Array2和Array1 指向同一段内存区域深拷贝Array2和Array1 分别指向不同的内存区域

从这一点来看我们刚刚所说的是正确的,

非集合拷贝

不可变字符串(NSString)

代码

 NSString *string = @"123"; NSString *stringCopy = [string copy]; NSMutableString *stringMutable = [string mutableCopy]; NSLog(@"string addrerss:%p",string); NSLog(@"stringCopy addrerss:%p",stringCopy); NSLog(@"stringMutable addrerss:%p",stringMutable); 

打印结果

请添加图片描述

  • NSString 进行copy 只是对其指针拷贝
  • NSString 进行 mutableCopy 是真正创建一份新的对象

不可变的字符串是存在内存常量,因此可以看到两处地址相差甚远

可变字符串(NSMutableString)

 NSMutableString *mString = [[NSMutableString alloc]initWithString:@"123"]; NSString *mStringCopy = [mString copy]; NSString *mStringMutable = [mString mutableCopy]; NSLog(@"mString addrerss:%p",mString); NSLog(@"mStringCopy addrerss:%p",mStringCopy); NSLog(@"mStringMutable addrerss:%p",mStringMutable); NSLog(@"mStringCopy is mutable ? %@",[mStringCopy isKindOfClass:[NSMutableString class]] ? @"YES" : @"NO"); NSLog(@"mStringMutable is mutable ? %@",[mStringMutable isKindOfClass:[NSMutableString class]] ? @"YES" : @"NO"); 

请添加图片描述

  • NSMutableString 的copy 变成是一个不可变的字符串
  • mutableCopy 返回的是一个可变的字符串
  • NSMutableString 无论copy还是mutableCopy 地址都不一样

总结
请添加图片描述

集合类对象拷贝(copy/mutableCopy)

集合类不可变对象

 NSString *str1 = @"hello world"; NSMutableString *str2 = [NSMutableString stringWithString:@"hello world"]; NSArray *array1 = [NSArray arrayWithObjects: str1, str2, nil]; NSArray *array2 = [array1 copy]; NSArray *array3 = [array1 mutableCopy]; NSLog(@"\n array1 = %p class = %@ \n", array1, [array1 class]); NSLog(@"\n array2 = %p class = %@ \n", array2, [array2 class]); NSLog(@"\n array3 = %p class = %@ \n", array3, [array3 class]); NSLog(@"\n\n======== 元素是String ======== "); NSLog(@"\n obj0 = %p class = %@ \n", array1[0], [array1[0] class]); NSLog(@"\n obj0 = %p class = %@ \n", array2[0], [array2[0] class]); NSLog(@"\n obj0 = %p class = %@ \n", array3[0], [array3[0] class]); NSLog(@"\n\n======== 元素是mutableString ========"); NSLog(@"\n obj1 = %p class = %@ \n", array1[1], [array1[1] class]); NSLog(@"\n obj1 = %p class = %@ \n", array2[1], [array2[1] class]); NSLog(@"\n obj1 = %p class = %@ \n", array3[1], [array3[1] class]); 

输出

2021-05-08 17:34:32.+0800 AlgorithmDemo[39309:] array1 = 0x class = __NSArrayI array2 = 0x class = __NSArrayI array3 = 0x class = __NSArrayM ======== 元素是String ======== obj0 = 0x class = __NSCFConstantString obj0 = 0x class = __NSCFConstantString obj0 = 0x class = __NSCFConstantString ======== 元素是mutableString ======== obj1 = 0xb0 class = __NSCFString obj1 = 0xb0 class = __NSCFString obj1 = 0xb0 class = __NSCFString 

由打印可得:

  • 集合类不可变对象的copy为浅拷贝
  • mutableCopy为深拷贝
  • 无论copy还是mutableCopy集合内元素都是浅拷贝

集合类可变对象

 NSString *str1 = @"hello world"; NSMutableString *str2 = [NSMutableString stringWithString:@"hello world"]; NSMutableArray *array1 = [NSMutableArray arrayWithObjects: str1, str2, nil]; NSMutableArray *array2 = [array1 copy]; NSMutableArray *array3 = [array1 mutableCopy]; NSLog(@"\n array1 = %p class = %@ \n", array1, [array1 class]); NSLog(@"\n array2 = %p class = %@ \n", array2, [array2 class]); NSLog(@"\n array3 = %p class = %@ \n", array3, [array3 class]); NSLog(@"\n\n======== 元素是mutableString ========"); NSLog(@"\n obj0 = %p class = %@ \n", array1[0], [array1[0] class]); NSLog(@"\n obj0 = %p class = %@ \n", array2[0], [array2[0] class]); NSLog(@"\n obj0 = %p class = %@ \n", array3[0], [array3[0] class]); NSLog(@"\n\n======== 元素是String ======== "); NSLog(@"\n obj1 = %p class = %@ \n", array1[1], [array1[1] class]); NSLog(@"\n obj1 = %p class = %@ \n", array2[1], [array2[1] class]); NSLog(@"\n obj1 = %p class = %@ \n", array3[1], [array3[1] class]); 

输出:

2021-05-08 17:40:01.+0800 AlgorithmDemo[40202:] array1 = 0xe0 class = __NSArrayM array2 = 0x class = __NSArrayI array3 = 0x class = __NSArrayM ======== 元素是mutableString ======== obj0 = 0x class = __NSCFConstantString obj0 = 0x class = __NSCFConstantString obj0 = 0x class = __NSCFConstantString ======== 元素是String ======== obj1 = 0x class = __NSCFString obj1 = 0x class = __NSCFString obj1 = 0x class = __NSCFString 

结论

  • 集合类可变对象的mutableCopy和copy都是深拷贝
  • 但集合内元素是浅拷贝

总结

非集合类对象只有不可变对象的copy是浅拷贝,其它都是深拷贝 集合类且集合内元素都是浅拷贝

请添加图片描述

也可换个方式总结:

  • copy: 对不可变对象是浅拷贝,可变对象是深拷贝
  • mutableCopy: 始终是深拷贝
  • 无论深浅拷贝,集合对象内元素都是浅拷贝

集合里面元素拷贝(copyItems: )

集合浅拷贝

集合浅拷贝的方式很多,当创建一个浅拷贝时,之前集合里面的每个对象都会收到retain的消息,使其引用计数加1,并将其指针拷贝到新的集合中。

 NSArray *shaoolwCopyArray = [someArray copyWithZone:nil]; NSDictionary *shaoolwCopyDict = [[NSDictionary alloc]initWithDictionary:someDict copyItems: NO]; 

数组浅拷贝

代码

 NSArray *someArray = @[@222]; NSArray *shaoolwCopyArray = [someArray copyWithZone:nil]; NSLog(@"someArray addrerss:%p",someArray); NSLog(@"shaoolwCopyArray addrerss:%p",shaoolwCopyArray); 

打印后的地址
请添加图片描述

浅拷贝的数组所指向的地址是一样的

这是因为对于数组我们只调用了它的copyWithZone方法,由于是不可变的数组,返回了自身,所以浅拷贝前后不可变数组内存地址不变

字典浅拷贝

代码

 NSDictionary *someDict = @{ 
   @"11":@"22"}; NSDictionary *shaoolwCopyDict = [[NSDictionary alloc]initWithDictionary:someDict copyItems: NO]; NSLog(@"someDict addrerss:%p",someDict); NSLog(@"shaoolwCopyDict addrerss:%p",shaoolwCopyDict); 

打印后的地址

请添加图片描述

字典所指向的内存地址发生了变化,其内部元素的地址不变。

而对于字典来说,shaoolwCopyDict是通过alloc和init 创建的,因此在内存中开辟了一段新的内存空间,所以浅拷贝前后字典的内存地址发生了变化.但对于之前字典中的对象,只是拷贝其内存地址,

如果代码改变一下

NSDictionary *someDict = @{ 
   @"11":@"22"}; NSDictionary *shaoolwCopyDict = [someDict copy]; 

打印结果

请添加图片描述

NSDictionary 它是不可变的使用copy 之后返回自身

浅拷贝总结

浅拷贝并不是自身的浅拷贝,而是对于其内部元素的浅拷贝

集合深拷贝

对于深拷贝将copyItems:换成YES就是深拷贝了。

深拷贝中,系统会向集合中的每一个元素对象发送一个copyWithZone:消息,改消息是来自与NSCopying协议,如果有对象没有实现改协议方法,那么就会导致崩溃,如果实现了改方法,那么会根据该方法的具体实现。

数组深拷贝

数组深拷贝(元素不可变NSString)
代码

 NSString *str = @"2222"; NSArray *someArray = @[str]; NSArray *shaoolwCopyArray = [someArray copyWithZone:nil]; NSArray *deepCopyArray = [[NSArray alloc]initWithArray:someArray copyItems:YES]; NSLog(@"someArray addrerss:%p",someArray); NSLog(@"shaoolwCopyArray addrerss:%p",shaoolwCopyArray); NSLog(@"deepCopyArray addrerss:%p",deepCopyArray); NSLog(@"someArray[0] addrerss:%p",someArray[0]); NSLog(@"shaoolwCopyArray[0] addrerss:%p",shaoolwCopyArray[0]); NSLog(@"deepCopyArray[0] addrerss:%p",deepCopyArray[0]); 

猜测结果shaoolwCopyArray和deepCopyArray应该是不一样的,这是因为deepCopyArray是通过alloc ,init 创建的,但最起码这两个数组的首元素地址是不一样的,运行后发现结果

运行结果

请添加图片描述

前三行与我们猜测的一致。但后面三行却 打印了相同的地址,然后后面也打印了一样的,明明采用了深浅拷贝,出现的结果确是相同的内存地址。

原因

集合类型的深拷贝会对每个元素调用copyWithZone:方法,

这就意味着刚刚后面三行打印的结果取决于该方法,在深拷贝时对于第一个元素调用了
NSString的copyWithZone

:,但由于NSString是不可变的,对于其深拷贝创建一个新的内存是无意的,所以我们也可以猜测NSString的copyWithZone:返回的是self.所以浅拷贝时直接拷贝元素地址,而深拷贝时通过copyWithZone:方法来获取元素地址,两个结果是一样的。

数组深拷贝(元素可变NSMutableString)

将数组里面的NSString元素改变为NSMutableString

代码

 NSMutableString *str = [[NSMutableString alloc]initWithString:@"2222"]; NSArray *someArray = @[str]; NSArray *shaoolwCopyArray = [someArray copyWithZone:nil]; NSArray *deepCopyArray = [[NSArray alloc]initWithArray:someArray copyItems:YES]; NSLog(@"someArray addrerss:%p",someArray); NSLog(@"shaoolwCopyArray addrerss:%p",shaoolwCopyArray); NSLog(@"deepCopyArray addrerss:%p",deepCopyArray); NSLog(@"someArray[0] addrerss:%p",someArray[0]); NSLog(@"shaoolwCopyArray[0] addrerss:%p",shaoolwCopyArray[0]); 

运行结果

请添加图片描述

集合完全拷贝

就是对对象的每一层都是重新创建的实例变量,不存在指针拷贝

例子(数组的归档解挡)

在对数组的归档解挡时就是完全深拷贝

NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]]; 

三种集合拷贝总结
浅拷贝

在浅拷贝操作时,对于被拷贝对象的每一层都是指针拷贝

深拷贝

在深拷贝操作时,对于被拷贝对象,至少有一层是深拷贝

完全拷贝

在完全拷贝操作时,对于被拷贝对象,每一层都是深拷贝

面试题

copy 修饰符

一般用于修饰字符串和block以及一些字典和数组。

copy 修饰(字符串/字典/数组)
看下面的例子
请添加图片描述




打印结果是NO,如果把copy换成strong就变成YES了

copy修饰符分析

在使用copy时,会对属性的setter方法进行判断,对属性copy,根据属性是否为可变

  • 可变返回一个新的不可变对象,即创建一个新不可变字符串
  • 而对于不可变返回自身self,即为可变字符串。

strong修饰符分析

strong 则是对其引用,并没有执行copy方法

数组,字典,字符串,原理是一样的

对于NSString 作为不可变的字符串来说

NSString 不可变的对象,使用strong和copy是一样的

copy 修饰(block)

在ARC中如果引用外部变量赋值时便会自动拷贝到内存中

ARC 下使用block 使用copy/strong无异

这是为什么,数组浅拷贝不变,而字典浅拷贝发生了变化???

案例题(copyItems: 方法的讨论)

假设有一个Person类它有一个属性name,放入可变数组/或者不可变数组,数组发生深浅拷贝

代码

 Person *a = [[Person alloc]init]; a.name = @"阿里巴巴"; Person *b = [[Person alloc]init]; b.name = @"今日头条"; NSMutableArray *mutA = [NSMutableArray arrayWithObjects:a,b, nil]; NSMutableArray *mutB = [mutA mutableCopy]; // Personerson @interface Person : NSObject @property (nonatomic, copy) NSString *name; @end 

mutA 与 mutB 地址是否一样

 NSLog(@"mutA addrerss:%p",mutA); NSLog(@"mutB addrerss:%p",mutB); 

答案

mutA addrerss:0xbba0 mutB addrerss:0xbe10 深拷贝数组地址不一样 

mutA 与 mutB 里面元素的地址是否一样

 NSLog(@"mutA[0] addrerss:%p",mutA[0]); NSLog(@"mutB[0] addrerss:%p",mutB[0]); 

答案

mutA[0] addrerss:0xf6c3d0 mutB[0] addrerss:0xf6c3d0 里面元素的地址一样 

mutA 与 mutB 当改变mutB 里面元素的name值,mutA 里面的值改变不

请添加图片描述

因为 mutA 与 mutB 里面的元素地址是一样的,所以改变 mutB 里面的值, mutA 也会被改变。 

mutA 与 mutB 当改变mutB 里面元素的name值时,mutA 的值不变

copyItems: 等于YES

请添加图片描述

 NSMutableArray *mutA = [NSMutableArray arrayWithObjects:a,b, nil]; NSMutableArray *mutB = [[NSMutableArray alloc] initWithArray:mutA copyItems:YES]; 
// Person - (id)copyWithZone:(NSZone *)zone { Person *person = [[Person allocWithZone:zone] init]; person.name = self.name; return person; } 

copyItems: 等于YES 系统会向集合中的每一个元素对象发送一个copyWithZone:消息 而在copyWithZone: 的时候我创建了一个新对象

数组的归档解挡
#import "Person.h" @implementation Person - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.name = [aDecoder decodeObjectForKey:@"name"]; } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.name forKey:@"name"]; } @end 

请添加图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/226200.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 上午7:36
下一篇 2026年3月17日 上午7:37


相关推荐

  • 剑指Offer面试题:7.斐波那契数列

    一题目:斐波那契数列二效率很低的解法很多C/C++/C#/Java语言教科书在讲述递归函数的时候,大多都会用Fibonacci作为例子,因此我们会对这种解法烂熟于心上述递归的解法有很严重的效

    2021年12月19日
    40
  • 在form里面,放了四个UEditor,怎么在后台分别获取它们值

    在form里面,放了四个UEditor,怎么在后台分别获取它们值

    2021年9月18日
    59
  • java 四舍五入保留小数的几种方式

    java 四舍五入保留小数的几种方式引用处:https://blog.csdn.net/u014704879/article/details/41479399/https://blog.csdn.net/shruber/article/details/78413706https://www.cnblogs.com/Dhouse/p/7776780.html方式一:BigDecimaldoubletpD=6.1…

    2022年5月21日
    39
  • Android AsyncTask 源码解析

    Android AsyncTask 源码解析转载请标明出处 http blog csdn net lmj article details 本文出自 张鸿洋的博客 1 概述相信大家对 AsyncTask 都不陌生 对于执行耗时任务 然后更新 UI 是一把利器 当然也是替代 Thread Handler 的一种方式 如果你对 Handler 机制还不了解 请看 Android 异步消息处理机制让你深入理解 Lo

    2026年3月19日
    2
  • noip2014普及组初赛答案_csp提高组一等奖

    noip2014普及组初赛答案_csp提高组一等奖题目背景NOIP2011提高组DAY2试题3。题目描述风景迷人的小城Y市,拥有n个美丽的景点。由于慕名而来的游客越来越多,Y市特意安排了一辆观光公交车,为游客提供更便捷的交通服务。观光公交车在第0分钟出现在1号景点,随后依次前往2、3、4……n号景点。从第i号景点开到第i+1号景点需要Di分钟。任意时刻,公交车只能往前开,或在景点处等待。设…

    2026年2月20日
    4
  • 西门子PLC-1200 SCL语言开发学习笔记 (一)

    西门子PLC-1200 SCL语言开发学习笔记 (一)一、简介和背景PLC一般使用梯形图开发,但是梯形图适合电工使用而不是程序员使用,对我们来说开发困难,门槛高,幸好PLC的开发标准还带了类pascal的高级语言,在西门子这里叫SCL语言,这对于我们程序员来说门槛就很低了。要开发好复杂PLC逻辑,梯形图困难重重,市场上要价颇高,而使用SCL语言则非常合适处理复杂逻辑以及运算。二、新建SCL程序块在博图软件的项目视图中,便有添加新快,双击推荐使用FB模块,便于存放变量,语言选择SCL三、变量的创建和访问在打开…

    2022年8月31日
    9

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号