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

之前有一篇关于 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 的文档上看到这么一幅图:

谈谈 iOS 中可变对象与不可变对象那些事儿_第1张图片
浅拷贝与深拷贝

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

你可能感兴趣的:(谈谈 iOS 中可变对象与不可变对象那些事儿)