在编写程序的过程中,我们经常会想将某些对象进行复制用。在OC中,这样的复制通过copy方法实现,比如[someObject copy]。这样做的必要条件是该对象遵守NSCopying协议。
对于我们自定义的类,当我们想调用copy进行复制的时候,就需要自己来实现NSCopying协议的方法:
- (id)copyWithZone:(nullable NSZone *)zone;
NSZone
还是先理解下NSZone这个东西,在实际开发中我们几乎不会运用它。
目前我也只在2个方法中见过其作为参数存在:
+ (id)allocWithZone:(nullable NSZone *)zone;
- (id)copyWithZone:(nullable NSZone *)zone;
NSZone其实并不是一个对象,是一个C结构体,用来记录内存管理的一些信息。
我觉得可以理解为一片内存空间,在App运行的时候,系统默认分配一块Zone来管理我们这个App的所有内存中的对象,因此我们在开发中几乎不会去接触NSZone。
但是随着对象的不停alloc 和dealloc ,内存会变的碎片化,iOS为了解决这个问题,会将新对象分配到这些碎片的空隙中来进行内存填补,一般来说对象比较少的时候,这样也不会造成系统性能的亏损。但是对象一多,就不一定了。
由此引出一个问题,我们能不能专门创建一个空间,来保存一系列大量的对象,减少系统寻找“内存空隙”的时间来提高一些性能,答案是可以的。这时候NSZone就可以派上用场,把你的对象一起分配在一个自定的NSZone里面,手动管理起来就会方便很多。
NSZone的释放还不会用,估计还要切换成MRC,实在不会就不讲了。
让你的对象支持Copy
为了让你的对象支持Copy,只要两个步骤:
- 1 声明对象遵守
protocol - 2 重写- (id)copyWithZone:(nullable NSZone *)zone;方法
对于简单的情况,我们可以如下实现:
Person.h :
#import
@interface Person : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName;
@end
Person.m :
#import "Person.h"
@implementation Person
- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName
{
if (self = [super init]) {
_firstName = [firstName copy];
_lastName = [lastName copy];
}
return self;
}
- (id)copyWithZone:(NSZone *)zone
{
//直接调用init重新创建一个一模一样的对象
return [[[self class] alloc] initWithFirstName:_firstName lastName:_lastName];
}
@end
当然以上部分只适用于简单对象的copy处理。对于复杂一点的,比如我们的person包含一个数组名为family的数组属性,这时候copyWithZone:方法又该如何重写呢?如下
- (id)copyWithZone:(NSZone *)zone
{
Person *selfCopy = [[[self class] alloc] initWithFirstName:_firstName lastName:_lastName];
//对特殊属性特殊Copy处理。
[selfCopy.family = self.family mutableCopy];
return selfCopy;
}
OC中的拷贝
mutableCopy和copy
举个例子好了:
[NSMutableArray copy] -> NSArray
[NSArray mutableCopy] ->NSMutableArray
深浅拷贝
网络上说明很多,简单来说就是是否是拷贝指针还是内存空间的重新分配。
区别在于,举个例子:如果浅拷贝,那么原本对象容器中的数据改变,那么虽然副本容器地址不会变,但是容器中的对象也随之改变了。深拷贝则不会改变。
所以具体使用哪种还是从实际情况出发。
对于对象的深浅拷贝,还是总结一下:
- 1非容器类(NSString,NSNumber)
直接放结论:
对象是不可变的对象: mutableCopy ->深拷贝 -> 可变对象
copy ->浅拷贝 -> 不可变对象
对象是可变对象: mutableCopy ->深拷贝 -> 可变对象
copy ->深拷贝 -> 不可变对象
- 2 容器类(NSArray,NSDictionary)
对于容器类的本身,也就是对于NSArray对象来说,规则和上面的是一样的,关键是容器内的对象。
NSString *str1 = @"string1";
NSArray *array = @[str1];
NSArray *copyArray = [array copy];
NSLog(@"===========\n array:%p, copyArray:%p\n str1:%p copyArray[0]:%p",array,copyArray,str1,copyArray[0]);
输出如下:
===========
array:0x7fa90b415ed0, copyArray:0x7fa90b415ed0**
str1:0x10b5a1250 copyArray[0]:0x10b5a1250**
NSString *str1 = @"string1";
NSArray *array = @[str1];
//注意下面这行代码的改变
NSMutableArray *copyArray = [array mutableCopy];
NSLog(@"===========\n array:%p, copyArray:%p\n str1:%p copyArray[0]:%p",array,copyArray,str1,copyArray[0]);
输出如下:
===========
array:0x7fa37b70ef00, copyArray:0x7fa37b758060**
str1:0x10834a250 copyArray[0]:0x10834a250**
由此可见,对于容器中的对象,mutableCopy和copy都是浅复制(可变容器也一样)。
如果涉及到容器中包含容器,那么还有深复制和完全深复制之说,完全深复制就是容器中每一层对象都是深复制,而深复制就是只复制一层的对象。如何使用都是看具体情况,不展开了。
对于任何遵守NSCopying的对象来说,实现的都应该是浅复制,因为浅复制使用是最多的。
总结
- 1 想让你的对象支持复制的话,遵守NSCopying协议并实现copyWithZone:方法。
- 2 如果你的对象支持不可变和可变两种分类,那么需要实现mutableCopy和copy两种方法
- 3 区分深浅拷贝并看情况使用