谈谈 iOS 中可变对象与不可变对象那些事儿

作者: afishhhhh | 来源:发表于2016-10-21 15:03 被阅读1155次

之前有一篇关于 NSString文章,这篇算是另一个补充。

在上一篇文章提到过 NSMutableString 需要用 strong 修饰,而 NSString 需要用 copy 修饰,这对于所有的可变对象和不可变对象都是适用的。所以在下面的代码中,NSArray 类型的 childName 是用 copy 修饰的。

// Person.h
@interface Person : NSObject
@property (nonatomic, copy) NSArray *childName;
@end
// Person.m
@implementation Person
- (instancetype)init {
  self = [super init];
  if (self) {
    _childName = [NSArray array];
  }
  return self;
}
@end
// main.m
Peerson *p = [Person new];
NSMutableArray *newChildName = [NSMutableArray arrayWithObjects:@"a", @"b", nil];
p.childName = newChildName;

当执行 p.childName = newChildName 语句的时候,newChildName 会产生一个不可变的副本然后赋值给 p.childName

copymutableCopy

简单讲,copy 就是拷贝出一份不可变的副本,而 mutableCopy 就是拷贝出一份可变的副本。

通过栗子来观察细节。

不可变对象

NSArray *mArray1 = [NSArray arrayWithObjects:@"a", @"b", nil];
NSArray *mArray2;
mArray2 = [mArray1 copy];
NSLog(@"mArray1 address: %p", mArray1);
NSLog(@"%@", mArray1);
NSLog(@"mArray2 address: %p", mArray2);
NSLog(@"%@", mArray2);

在这里的输出结果,mArray1mArray2 是指向同一地址的,将 copy 作用在一个不可变的对象上也就没必要再拷贝出一份不可变对象。

改变一:

NSArray *mArray2;
mArray2 = [mArray1 copy];
// 修改为
NSMutableArray *mArray2;
mArray2 = [mArray1 mutableCopy];

mutableCopy 会产生一个可变的副本,对 mArray2 的修改则不会影响到 mArray1

可变对象

NSMutableArray *mArray1 = [NSMutableArray arrayWithObjects:@"a", @"b", nil];
NSArray *mArray2;
mArray2 = [mArray1 copy];
NSLog(@"mArray1 address: %p", mArray1);
NSLog(@"%@", mArray1);
NSLog(@"mArray2 address: %p", mArray2);
NSLog(@"%@", mArray2);

从输出结果中可以看到,当 copy 作用在一个可变对象上时,会产生一个不可变的副本。

改变一:

NSArray *mArray2;
mArray2 = [mArray1 copy];
// 修改为
NSMutableArray *mArray2;
mArray2 = [mArray1 mutableCopy];

mutableCopy 作用在一个可变对象上时,会产生一个可变的副本。

以上的结果还是显而易见的,接下来是关于浅拷贝和深拷贝。

浅拷贝和深拷贝

对于 NSArrayNSDictionary 等集合类对象来说,存在着浅拷贝和深拷贝的区别。因为它们都是一个容器,当被拷贝的时候,容器中的元素可能仅仅是拷贝了引用而不是重新开辟一块内存空间用来存储元素值。

在网上有些文章将浅拷贝、深拷贝与 copymutableCopy 联系了起来,但自己试验一下发现其实并没有必然的联系。

不可变对象

NSArray *mArray1 = [NSArray arrayWithObjects:@"a", @"b", nil];
NSArray *mArray2;
mArray2 = [mArray1 copy];
for (int i = 0; i < [mArray1 count]; i++) {
  NSLog(@"mArray1[%d] address: %p", i, mArray1[i]);
}
for (int i = 0; i < [mArray2 count]; i++) {
  NSLog(@"mArray2[%d] address: %p", i, mArray2[i]);
}
// mArray1[0] address: 0x10d3d35d0
// mArray1[1] address: 0x10d3d35f0
// mArray2[0] address: 0x10d3d35d0
// mArray2[1] address: 0x10d3d35f0

我们可以看到当使用 copy 的时候,因为是作用在不可变对象上,并没有产生一个新的副本,所以 mArray1mArray2 的元素地址都相同。

改变一:

NSArray *mArray2;
mArray2 = [mArray1 copy];
// 修改为
NSMutableArray *mArray2;
mArray2 = [mArray1 mutableCopy];
// mArray1[0] address: 0x1001625d0
// mArray1[1] address: 0x1001625f0
// mArray2[0] address: 0x1001625d0
// mArray2[1] address: 0x1001625f0

当使用 mutableCopy 的时候,mArray1 产生一个可变的副本,但是这个副本内部的元素地址仅仅是拷贝了引用。

也就是说,无论是 copy 还是 mutableCopy,当作用在不可变的集合类对象上的时候,其内部的元素仅仅是发生了浅拷贝,拷贝了一份引用。

可变对象

NSMutabelArray *mArray1 = [NSMutableArray arrayWithObjects:@"a", @"b", nil];
NSArray *mArray2;
mArray2 = [mArray1 copy];
for (int i = 0; i < [mArray1 count]; i++) {
  NSLog(@"mArray1[%d] address: %p", i, mArray1[i]);
}
for (int i = 0; i < [mArray2 count]; i++) {
  NSLog(@"mArray2[%d] address: %p", i, mArray2[i]);
}
// mArray1[0] address: 0x1005b25d0
// mArray1[1] address: 0x1005b25f0
// mArray2[0] address: 0x1005b25d0
// mArray2[1] address: 0x1005b25f0

改变一:

NSArray *mArray2;
mArray2 = [mArray1 copy];
// 修改为
NSMutableArray *mArray2;
mArray2 = [mArray1 mutableCopy];
// mArray1[0] address: 0x100ed75d0
// mArray1[1] address: 0x100ed75f0
// mArray2[0] address: 0x100ed75d0
// mArray2[1] address: 0x100ed75f0

我们发现,无论是 copy 还是 mutableCopy,当作用在可变对象上的时候,虽然产生了一个副本,但是其内部元素的地址依然没有改变,也就是说其内部的元素仅仅拷贝了引用。

可能存在以下情况:

NSMutableArray *mArray1 = [NSMutableArray arrayWithObjects:
                          [NSMutableString stringWithString:@"a"],
                          [NSMutableString stringWithString:@"b"],
                          nil];
NSArray *mArray2;
mArray2 = [mArray1 copy];
[mArray1[0] appendString:@"aa"];
NSLog(@"%@ %@", mArray1[0], mArray2[0]);
// aaa aaa

mArray1 的内部是可变的字符串,随后 mArray1 产生了一个不可变的副本赋值给 mArray2。但是如果此时改变 mArray1 内部的元素,则会影响到 mArray2

简单总结一下:

先讲一下我所理解的浅拷贝与深拷贝,我认为浅拷贝仅仅是拷贝了引用,无论是拷贝了整个集合类对象的引用,还是拷贝了其中元素的引用,都是浅拷贝。而深拷贝是需要产生一个完全不一样的副本的,整个集合类对象包括其中的元素都完全不一样。

对于集合类对象来说,无论是可变还是不可变,无论是发送 copy 消息还是 mutableCopy 消息,区别仅仅在于 copy 产生的副本是不可变的,而 mutableCopy 产生的副本是可变的,都仅仅是浅拷贝而已,并没有发生所谓的深拷贝,而深拷贝需要通过其他的方法。

[immutableObject copy]
[immutableObject mutableCopy]
[mutableObject copy]
[mutableObject mutableCopy]

都是浅拷贝。

但是!

在网上有人说,对于不可变的集合类对象,copy 是浅拷贝,因为只拷贝了一份指针,而 mutableCopy 是单层深拷贝,因为产生了一份副本,虽然集合类对象的元素依然是复制指针。对于可变的集合类对象来说,copymutableCopy 都是单层深拷贝。

也就是说他们认为浅拷贝仅仅是拷贝了整个集合类对象的引用,如果其中元素的引用也发生了拷贝即是深拷贝。

[immutableObject copy]
// 浅拷贝
[immutableObject mutableCopy]
// 单层深拷贝
[mutableObject copy]
// 单层深拷贝
[mutableObject mutableCopy]
// 单层深拷贝

我在 Apple 的文档上看到这么一幅图:

浅拷贝与深拷贝

这张图中都是集合类对象,浅拷贝是产生了 Array1 的副本 Array2Array2 中的元素依然指向原来的元素,也就是说从这张图来看,[immutableObject mutableCopy][mutableObject copy][mutableObject mutableCopy] 都仅仅是浅拷贝而已。

相关文章

网友评论

  • Storydo:说的不错,不过我觉得没必要追究的那么清楚,自己明白怎么回事就好了,网上关于深浅拷贝也没有一致的说法,我个人倒是比较认同你这种说法
  • Dombo_Y:厉害了,前两天听阳神讲过
    afishhhhh:@BusterMark 直播我也看了,以前我也没仔细思考过,就写了一下 :grin:
  • HoyaWhite:可变就是女朋友。。。不可变就是老婆、、、、 :smile:
  • 天口三水羊:有点问题 网上说的copy mutablecopy和深浅copy有关系是指的框架类 而不是自定义类 对于自定义类 无论是可变不可变还是 深浅copy 看的是你如何实现nscopying 你可以看我的文章 一起进步
    天口三水羊:@天口三水羊 嗯 你的一些想法是对的 copy是浅copy mutableCopy是深copy这句话是有问题的 但是 因为oc不开源 所以 这相对于一些框架类是适用的 而可变不可变与深浅对于自定义对象而言 仅仅是看你怎么实现nscopying协议的copywithzone方法
    afishhhhh:@天口三水羊 我看了一下你的文章,我说下自己的理解吧。对于非容器对象,浅拷贝就是复制一下指针,深拷贝复制一下内容,我觉得没啥问题,官方的文档上有一幅图就是这样。但是对于容器对象,我觉得只有把元素也完全复制了才能算是深拷贝,其他都是所谓的浅拷贝。另外就是你说的关于深浅拷贝和copy mutableCopy的关系,我文章里写的也比较含糊,就不管网上那些说法了。我讲一下自己的理解,copy和mutableCopy的重点应该是关注不可变和可变,而不应该去跟深浅拷贝扯上关系,深浅拷贝的关注点应该是是否完全复制了内容,而不是像网上文章说的copy实现了浅拷贝,mutableCopy实现了深拷贝
    afishhhhh:@天口三水羊 嗯,谢谢,晚上去你那儿看看

本文标题:谈谈 iOS 中可变对象与不可变对象那些事儿

本文链接:https://www.haomeiwen.com/subject/nzqcuttx.html