[iOS面试] 理解 copy

牧云@^-^@ 提交于 2019-12-27 05:27:02

前言

NSObject 有 copy 和 mutableCopy 两个成员。这两个函数是分别直接返回 copyWithZone 和 mutableCopyWithZone 的返回值。
因此,如果调用者没有实现 copyWithZone 或 mutableCopyWithZone,肯定会报错:unrecognized selector。
这两个函数,就分别是 NSCopying,NSMutableCopying 协议的成员。
(注意,不是这个:+ (id)copyWithZone:(struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;
这个是 MRC 项目可用,ARC 下直接无视该函数,涉及到 Zone 都用 null)

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

copy 和 mutableCopy

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

指针到底是什么?指针本质上是一个 size_t 大小的一个数,或者直接理解为 long 类型的一个变量,这个数就是一个地址。
指针复制,例如 NSArray* arr = …; NSArray* arr1 = [arr copy] 返回的就是一个数,这个数等于 arr。与对象无关。后面会介绍多态带来的问题。

便于理解:本来就不可变,复制有什么用?直接用旧的指针不就得了!

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

两个例子:
NSString,对象的复制(所有成员都被复制)
NSArray,容器类,容器也是对象,所以容器对象被复制了(所有成员,包括 count 等)。那么容器的内容呢?对于 NSArray 来说,内容保存在私有成员:id *_list 中,该指针被复制了,同时,理所应当的,和 容器的内容没关系啊~~

便于理解:本来我就不可变,我怎么给你可变的副本?降低一下要求,给你一份新的副本就不错了!

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

(返回的对象,设为 A,debug 时可能显示是 NSMutableString,但实际上是不可变的。因此 A 再调用 copy,返回的就是 A,指针复制,上面已经说过了)
(为什么 A 会显示为 NSMutableString ?如果不是显示 bug 的话,我给的一种解释是,由于编译器对代码有优化策略,返回的 A 的内容可能仍然和原对象的内容共享一片内存,直到原对象内容修改时才真正复制(注意是内容,对象应该是已经复制了)(纯属个人猜测)。)

便于理解:我的内容会变的,你想要一份副本,给你当前内容的快照吧。

可变对象的 mutableCopy:复制对象

字面意思,可变对象,要一份可变的副本。对象本身既要复制,还要保持可变。

容器类的复制

alloc,initWithArray: copyItems:

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

因为是 alloc、init,这个容器对象肯定是新的。

如果 第二个参数 copyItems 为 0,则直接引用,内容引用计数加一

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

所以说,使用 copyItems = 1 时容易有一个误会:如果内容是 NSString,实际上并没有复制内容对象,而是总会根据内容对象如何实现 NSCopying 而定。

多态与 strong / copy

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

由于多态,NSMutableString 对象可以赋值给父类 NSString 的声明。想象一下:
NSString* 声明为 strong,赋值一个 NSMutableString*,然后这个可变字符串在别处被更改了,变了。同理,其他不可变类型也要声明为 copy。
如果使用 copy,property 声明 copy 本质上是在 setter 里,设参数为 B,赋值的时候 = [B copy]; 这样的话,如果 B 是不可变字符串,反正不可变,无所谓;如果可变,返回不可变副本。后面即使强制转换该成员为 NSMutableString,变化时也会报错。

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

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

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

  1. 手动实现 setter。如果声明 copy,默认的 setter 调用 copy 函数,我调用 mutableCopy 不就解决了吗?这样解决的话就与声明 copy、strong 无关了,反正自己实现的。
  2. 声明 strong。这样虽然不用担心数组变成不可变的,但是别的地方只要得到了其指针就可以修改这个数组。这看业务需求是否需要在外部修改,不过我不建议这么设计(辣鸡的设计)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!