[iOS 理解] copy

前言

NSObject 有 copy 和 mutableCopy 两个成员。这两个函数是分别直接返回 copyWithZone 和 mutableCopyWithZone 的返回值。
因此,如果调用者没有实现 copyWithZone 或 mutableCopyWithZone,肯定会报错:unrecognized selector。
这两个函数,就分别是 NSCopying,NSMutableCopying 协议的成员。

基础框架里的类,很多都实现了 NSCopying,NSMutableCopying,例如 NSString 可以调用 copy 而不报错。那么,这些基础框架是具体怎么实现的这两个协议,才导致了所谓的 “深浅拷贝” ?数组的 [[NSArray alloc] initWithArray: copyItems: ] 到底是干什么的? strong 与 copy 到底用哪个?这些是本文将要回答的。

copy 和 mutableCopy 规则

既然只是规则,其实我们也不一定要遵守。看几个系统类是如何实现 Copying 协议的。

不可变对象的 copy:“指针复制”

这类对象的代码是这样的:

- (id)copyWithZone:(nullable NSZone *)zone {
    return self;
}

NSArray* arr = [NSArray new];
NSArray* arr1 = [arr copy];
指针可以理解为 long 类型的一个数,这里的 copy 返回的就是一个数,这个数等于 arr。
(当然,ARC 下会 retain 一次,引用计数变为 2,本文只关注 copy 结果)
后面会介绍多态带来的问题。

“本来就不可变,复制有什么用?直接用旧的指针不就得了!”

不可变对象的 mutableCopy:复制为可变对象

一般会复制出一个 NSMutableXXX 开头的类的对象。 对于容器类,并不会复制容器内的 item,即直接多 retain 一次 item。

可变对象的 copy:复制对象为一份不可变的对象

例如 NSMutableString 类的对象调用 copy,返回值设为 A,则 A 不可变;
A 再调用 copy,返回的也仍是 A,指针复制,上面已经说过。

“我的内容会变的,你想要一份副本,为了内容有效,只能给你当前内容的快照了”

可变对象的 mutableCopy:复制对象

可变对象,要一份可变的副本。内部成员需要 retain 一遍,还要保持可变性。

小总结

我们实现 copy 时返回的对象最好是不可变的,甚至可以是原来的对象;mutableCopy 返回的对象最好是可变的。容器类,内容并不复制,而是直接 retain 一遍。总之如何实现也要看需求而定。

容器类的深拷贝

initWithArray: copyItems:

容器比如 Set,Dictionary 都有类似的初始化方法,这里以 NSArray 为例。

如果 第二个参数 copyItems 为 false,则直接引用容器内的对象。

如果 第二个参数 copyItems 为 true,那么在复制新容器时,对所有的 item 也都会调用 copy,返回值给新的容器。和前面说的一样,如果这个 item A 没有实现 copyWithZone(NSCopying 协议),就会报错,如果这个 A 是不可变的,就直接返回 A,可变的返回不可变副本。

所以说,使用 copyItems = 1 时容易有一个误会:如果内容是 NSString,实际上仍然没有复制内容对象,因为还要由内容对象本身去决定自己如何被复制、如何实现 NSCopying。

strong / copy

一个最基本的问题:在声明 property 的时候,NSString* 声明为 copy。为什么?

由于多态,NSMutableString 对象可以赋值给父类 NSString 的声明。想象一下:
NSString* 声明为 strong,被赋值一个 NSMutableString*,然后这个可变字符串在别处被更改了,那么 NSString* 也就跟着变了。同理,其他类似类型也要声明为 copy。

如果 @property 声明 copy,则在属性 setter 里,会调用新值的 copy,返回值作为真的新值。这样的话,如果新值是不可变字符串 NSString,反正不可变,无所谓;但如果可变,copy 返回不可变副本。不会再有上述 bug。

虽然很基础,但还要提一下:
NSString *str = [[NSMutableString alloc] initWithString:@"1"];
[str copy]; 这个 [str copy] 由于消息机制 实际上调用的是 NSMutableString 的 copyWithZone。

那么可变类型,声明 strong 还是 copy ?

同理,NSMutableArray 如果声明 copy,反而赋值后变成不可变的数组,后面就不能操作了。
解决办法有两个:

  1. 手动实现 setter。如果声明 copy,默认的 setter 调用 copy 函数不符合预期,我手动调用 mutableCopy 不就解决了吗?这样可以切断与原参数数组的联系,自己维护一个新的可变数组。但这和需求有关,如果想共同操作同一个数组,就得手动返回原指针。实现了 setter 就自己接管行为了,声明什么也无所谓了
  2. 声明 strong。始终共同维护一个数组对象。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值