高效编写代码的方法(十八):NSCopying协议

在编写程序的过程中,我们经常会想将某些对象进行复制用。在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 区分深浅拷贝并看情况使用

你可能感兴趣的:(高效编写代码的方法(十八):NSCopying协议)