简介
在大部分的编程语言中,都有深拷贝与浅拷贝的概念,如果使用深浅拷贝有误,可能会造成数据安全性的问题,那么本节针对OC中的深浅拷贝展开讨论
深拷贝与浅拷贝的概念
我们在进行实例对象操作时,无非是两种实例方式:
- 创建新对象后将原对象的内容拷贝一份,而后返回该对象
- 引用
深浅拷贝的区别由此产生:
- 深拷贝:重新开辟新的内存空间,完全拷贝该对象的值
- 浅拷贝:不重新开辟新的内存空间,引用该对象
以下是B对象拷贝A对象时,深浅拷贝的示意图:
那么根据上图,我们可以推论,浅拷贝方式下,如果A对象指向的内存空间上的值改变,也会影响B对象,即造成了安全性问题。深拷贝方式下,则不会有这种问题。那么既然浅拷贝会造成安全性问题,为何还要保留它呢?别急,且看下文。
不同类型对象下的深浅拷贝
在本节中,我们将OC中的对象分为四类:
- 可变非容器对象,例如NSMutableString,NSMutableArray等
- 不可变非容器对象,例如NSString,NSArray等
- 可变容器对象,例如NSMutableArray,NSMutableDictionary等
- 不可变容器对象,例如NSArray,NSDictionary等
在OC中,对象有两种拷贝的方式,即调用copy与mutableCopy方法。使用不同类型对象调用这两种方法,产生的拷贝类型(即深拷贝或浅拷贝)是不同的。
下图是不同类型对象分别调用copy与mutableCopy后产生的拷贝类型:
案例分析
接下来我们通过不同案例来对不同类型对象下的拷贝及原因进行分析
1. 不可变非容器对象下的拷贝
NSString *originalStr = @"something";
NSMutableString *copyStr = [originalStr copy];
NSMutableString *mutableStr = [originalStr mutableCopy];
NSLog(@"%p %p %p %@ %@",originalStr,copyStr,mutableStr,[copyStr class],[mutableStr class]);
输出
0x1084530f0 0x1084530f0 0x600000269cc0 __NSCFConstantString __NSCFString
那么通过这个案例,我们可以证明如下结论:
- 不可变非容器对象调用copy进行拷贝,是使用的浅拷贝,返回了该不可变对象指针(通过输出中的__NSCFConstantString可以验证)。原因是不可变对象在初始化时候已经固定了占用的内存大小,不会再进行改变。基于此前提,使用浅拷贝不可变对象时便不必担心原对象会改变而影响到新对象。此时不会产生安全性问题,且不必新申请一个对象空间,节省了内存,这就是上文提到的保留浅拷贝的原因。
- 不可变非容器对象调用mutableCopy进行拷贝,是用的是深拷贝,返回了一个新的可变对象指针(通过输出中的__NSCFString可以验证)。 那既然浅拷贝不可变对象已经实现了保证安全性且节省了内存空间,为何mutableCopy是深拷贝呢?作者本人也不理解,我认为应该是浅拷贝节省的内存空间其实也不多,且深拷贝也可以保证安全性,所以apple保留了它,给开发者选择。
2. 可变非容器对象下的拷贝
NSMutableString *originalStr = [NSMutableString stringWithString:@"something"];
NSMutableString *copyStr = [originalStr copy];
NSMutableString *mutableStr = [originalStr mutableCopy];
NSLog(@"%p %p %p %@ %@",originalStr,copyStr,mutableStr,[copyStr class],[mutableStr class]);
输出
0x60800007ee80 0xa0c0c60045412da9 0x60800007edc0 NSTaggedPointerString __NSCFString
那么通过这个案例,我们可以证明如下结论:
- 可变非容器对象调用copy进行拷贝,是用的深拷贝,返回了一个NSString的子类对象指针(NSTaggedPointerString其实是NSString的子类,是基于64位cpu下用于优化节省内存的对象)。因为原对象是可变类型,所以为了保证安全性,使用深拷贝。但是有人可能会问,原对象是一个可变类型对象,使用copy后返回的却是不可变对象呢?其实无论是返回可变对象不可变对象,对于新对象都没有任何影响,它只是指向了一个字符串,当新对象改变,本质是指针指向了新字符串。
- 可变对象调用mutableCopy进行拷贝,是用的也是深拷贝,返回了一个新的可变对象指针(通过输出中的__NSCFString可以验证),同样的保证了安全性。
3. 不可变容器对象下的拷贝
NSArray *originalArr = @[@1,@2,@[@3,@4]];
NSMutableArray *copyArr = [originalArr copy];
NSMutableArray *mutableArr = [originalArr mutableCopy];
NSLog(@"%p %p %p %@ %@",originalArr,copyArr,mutableArr,[copyArr class],[mutableArr class]);
输出
0x60800005c4d0 0x60800005c4d0 0x60800005c560 __NSArrayI __NSArrayM
那么通过这个案例,我们可以证明如下结论:
- 不可变容器对象调用copy进行拷贝,是用的浅拷贝,返回了一个不可变对象的指针(通过输出中的__NSArrayI 可以验证)。使用浅拷贝的原因和上面提过的一样,由于是不可变对象,所以不存在改变的情况,使用浅拷贝就可以保证安全性。
- 不可变容器对象调用mutableCopy进行拷贝,是用的深拷贝,返回了一个可变对象的指针(通过输出中的__NSArrayM 可以验证)。
4. 可变容器对象下的拷贝
NSMutableArray *originalArr = [[NSMutableArray alloc] initWithObjects:@1,@2,@[@3,@4], nil];
NSMutableArray *copyArr = [originalArr copy];
NSMutableArray *mutableArr = [originalArr mutableCopy];
NSLog(@"%p %p %p %@ %@",originalArr,copyArr,mutableArr,[copyArr class],[mutableArr class]);
输出
0x60000004ad10 0x60000004aef0 0x60000004ae60 __NSArrayI __NSArrayM
那么通过这个案例,我们可以证明如下结论:
- 可变容器对象调用copy进行拷贝,是用的深拷贝,返回了一个不可变对象的指针(通过输出中的__NSArrayI 可以验证),关于为什么返回的是不可变对象我也不理解。使用浅拷贝的原因和上面提过的一样,由于是不可变对象,所以不存在改变的情况,使用浅拷贝就可以保证安全性。
- 可变容器对象调用mutableCopy进行拷贝,是用的深拷贝,返回了一个可变对象的指针(通过输出中的__NSArrayM 可以验证)。
经典面试题
看完了上面的内容,我们来回答一个面试题:为什么NSString的属性一般都是使用copy修饰?
假设使用copy修饰了一个NSString对象:
@property (nonatomic,copy) NSString *str;
那么系统会给str对象自动生成setter方法,模拟setter实现代码如下:
- (void)setter:(NSString *)strNew {
if(_str != strNew) {
_str = [strNew copy];
}
}
观察setter方法,我们发现使用copy后,浅拷贝了该strNew对象,即str指向了strNew指向的那块内存,避免了内存浪费。
结束
关于深拷贝与浅拷贝个人的理解到此结束,作者水平有限,文中可能存在错误,敬请谅解指教。
歌曲推荐:家后-江蕙