详解深浅拷贝

前言

说道拷贝大家可能联想到深拷贝和浅拷贝概念,然而在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 是真正创建一份新的对象

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

1.NSString 是不可变的,对其copy也仍然是相同的内容,因此copy后返回自身,所以仍然是相同的地址
2.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 地址都不一样

总结
请添加图片描述

1.自定义的类需要实现深拷贝和浅拷贝,只要实现NSCoping协议即可。
2.如果实现浅拷贝,只需要在copyWithZone:方法返回自身即可
3.深拷贝:取决于每一层对象的本身,这个需要完全拷贝。则每一层对象都应该实现copyWithZone:中创建好新的对象,如果每一层都做到了深拷贝,那么最外层就是完全拷贝。

集合类对象拷贝(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.740896+0800 AlgorithmDemo[39309:14557776] 
 array1 = 0x102904180 class = __NSArrayI
 array2 = 0x102904180 class = __NSArrayI
 array3 = 0x102904260 class = __NSArrayM

======== 元素是String ======== 
 obj0 = 0x100008220 class = __NSCFConstantString
 obj0 = 0x100008220 class = __NSCFConstantString
 obj0 = 0x100008220 class = __NSCFConstantString

======== 元素是mutableString ========
 obj1 = 0x1028719b0 class = __NSCFString
 obj1 = 0x1028719b0 class = __NSCFString
 obj1 = 0x1028719b0 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.757406+0800 AlgorithmDemo[40202:14563979] 
 array1 = 0x1006046e0 class = __NSArrayM
 array2 = 0x100604860 class = __NSArrayI
 array3 = 0x100604890 class = __NSArrayM

======== 元素是mutableString ========
 obj0 = 0x100008220 class = __NSCFConstantString
 obj0 = 0x100008220 class = __NSCFConstantString
 obj0 = 0x100008220 class = __NSCFConstantString

======== 元素是String ======== 
 obj1 = 0x100604510 class = __NSCFString
 obj1 = 0x100604510 class = __NSCFString
 obj1 = 0x100604510 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 作为不可变的字符串来说

1.修饰符copy返回的是自身,执行copy方法仍然返回自身。
2.strong 修饰也是返回自身,

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

copy 修饰(block)

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

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

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

这是因为对于数组我们只调用了它的copyWithZone方法,由于是不可变的数组,返回了自身,所以浅拷贝前后数组内存地址不变,而对于字典来说,shaoolwCopyDict是通过alloc和init
创建的,因此在内存中开辟了一段新的内存空间。但对于之前字典中的对象,只是拷贝其内存地址,所以浅拷贝前后字典的内存地址发生了变化,

案例题(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:0x60000371bba0
mutB addrerss:0x60000371be10
深拷贝数组地址不一样

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

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

答案

mutA[0] addrerss:0x600003f6c3d0
mutB[0] addrerss:0x600003f6c3d0

里面元素的地址一样

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

请添加图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值