来自:Gua | 瓜地

链接:https://desgard.com/copy/ 


在初学 iOS 的时候,可能会被灌输这么一个常识,切记 NSString 的 property 的修饰变量要写作 copy ,而不是 strong,那么这是为什么?

经典面试题:为什么 NSString 类型成员变量的修饰属性用 copy 而不是 strong (或 retain ) ?

review Copy Operation

Test 1

先来模拟一个程序设计错误的场景。有一个叫做 Person 的 Class,其中它拥有一个 NSString 类型的 s_name 属性(代表 name 是 strong),我们想给一个对象的 s_name 赋值,并且之前的赋值变量还想重复使用到其他场景。所以,我们在引入这个 Class 的 ViewController 进行如下操作:

- (void)test1 {  
   self.one = [[Person alloc] init];  
   NSMutableString *name = [NSMutableString stringWithFormat:@"iOS"];  
   self.one.s_name = name;  
   NSLog(@"%@", self.one.s_name);  
   [name appendString:@" Source Probe"];  
   NSLog(@"%@", self.one.s_name);  
}

如果在 Person 这个 Class 中,我们的 s_name 的修饰属性是 strong 的话,会看到如下输出结果。

2016-08-12 05:51:21.262 TestCopy[64714:20449045] iOS  
2016-08-12 05:51:21.262 TestCopy[64714:20449045] iOS Source Probe

可是,我们操作的仅仅是对 s_name 那个变量,为什么连属性当中的 s_name 也会被改变?对这段代码稍做修改,重新测试。

Test 2

- (void)test2 {  
   self.one = [[Person alloc] init];  
   NSString *name = [NSMutableString stringWithFormat:@"iOS"];  
   self.one.s_name = name;  
   NSLog(@"%@", self.one.s_name);  
   name = @"iOS Source Probe";  
   NSLog(@"%@", self.one.s_name);  
}

这一次我们看到了输出结果是正常的:

2016-08-12 05:56:57.162 TestCopy[64842:20459179] iOS  
2016-08-12 05:56:57.162 TestCopy[64842:20459179] iOS

Test 3

再来做第三个实验,我们换用 copy 类型的成员 c_name,来替换实验1中的 s_name ,查看一下输出结果。

最后发现输出结果依旧是我们所需要的。

- (void)test3 {  
   self.one = [[Person alloc] init];  
   NSMutableString *name = [NSMutableString stringWithFormat:@"iOS"];  
   self.one.c_name = name;  
   NSLog(@"%@", self.one.c_name);  
   [name appendString:@" Source Probe"];  
   NSLog(@"%@", self.one.c_name);  
}
2016-08-12 06:03:40.226 TestCopy[64922:20479646] iOS  
2016-08-12 06:03:40.227 TestCopy[64922:20479646] iOS

做过如上三个实验,或许你会知道对 property 使用 copy 修饰属性的原因了。也就是在一个特定场景下:当我们通过一个 NSMutableString 对 NSString 变量进行赋值,如果 NSString 的 property 是 strong 类型的时候,就会随着 NSMutableString 类型的变量一起变化。

这个猜测是正确的。在 stackoverflow 上也对这个场景进行单独的描述。可是原因是什么?继续做下面的实验:

Test 4

- (void)test4 {  
   NSMutableString *str = [NSMutableString stringWithFormat:@"iOS"];  
   NSLog(@"%p", str);  
   NSString *str_a = str;  
   NSLog(@"%p", str_a);  
   NSString *str_b = [str copy];  
   NSLog(@"%p", str_b);  
}

输出地址后,我们发现以下结果:

2016-08-12 06:15:45.169 TestCopy[65230:20515110] 0x7faf28429e70   
2016-08-12 06:15:45.170 TestCopy[65230:20515110] 0x7faf28429e70  
2016-08-12 06:15:45.170 TestCopy[65230:20515110] 0xa00000000534f693

发现当令 NSString 对象指针指向一个 NSMutableString 的时候,则会对一个对象进行深复制。这也就是我们一直所说的在一个 Class 的成员是 NSString 类型的时候,修饰属性应该使用 copy ,其实就是在使用 mutable 对象进行赋值的时候,防止 mutable 对象的改变从而影响成员变量。从 MRC 的角度来看待修饰属性,若一个属性的关键字为 retain (可等同于 strong ),则在进行指针的指向修改时,如上面的self.one.name = str,其实是执行了self.one.name = [str retain],而 copy 类型的属性则会执行self.one.name = [str copy]。

而在 Test 2 中,我们的实验是将一个 NSString 对象指向另外一个 NSString 对象,那么如果前者是 copy 的成员,还会进行深复制吗?进行下面的 Test 5,我们令 c_name 的修饰变量为 copy。

Test 5

- (void)test5 {  
   self.one = [[Person alloc] init];  
   NSString *name = [NSMutableString stringWithFormat:@"iOS"];  
   self.one.c_name = name;  
   NSLog(@"%@", self.one.c_name);  
   name = @"iOS Source Probe";  
   NSLog(@"%@", self.one.c_name);  
   NSString *str = [NSString stringWithFormat:@"iOS"];  
   NSLog(@"%p", str);  
   NSString *str_a = str;  
   NSLog(@"%p", str_a);  
   NSString *str_b = [str copy];  
   NSLog(@"%p", str_b);  
}

发现结果符合猜测:

2016-08-12 08:09:28.125 TestCopy[66402:20671038] iOS  
2016-08-12 08:09:28.126 TestCopy[66402:20671038] iOS  
2016-08-12 08:09:28.126 TestCopy[66402:20671038] 0xa00000000534f693   2016-08-12 08:09:28.126 TestCopy[66402:20671038] 0xa00000000534f693   2016-08-12 08:09:28.126 TestCopy[66402:20671038] 0xa00000000534f693

从一个 NSString 进行 copy 后赋值,copy 方法仍旧是浅拷贝。这个效果就等同于str_b = [str retain],在 ARC 中即 str_b = str。

那么,如何在这种情况下,让str_b指向一个str的深拷贝呢,答案就是str_b = [str mutableCopy]。这也就是 copy 和 mutableCopy 的区别。

copy & mutableCopy

下面我们开始对 copy 和 mutableCopy 原理进行分析。以下也是我的源码学习笔记。

在opensource.apple.com的git仓库中的Runtime源码中有NSObject.mm这个文件,其中有如下方法是关于 copy 的:

- (id)copy {  
   return [(id)self copyWithZone:nil];  
}  
- (id)mutableCopy {  
   return [(id)self mutableCopyWithZone:nil];  
}

发现copy和mutableCopy两个方法只是简单调用了copyWithZone:和mutableCopyWithZone:两个方法。所以有了以下猜想:对于 NSString 和 NSMutableString,Foundation 框架已经为我们实现了 copyWithZone 和 mutableCopyWithZone 的源码。我在searchcode.com找到了 Hack 版的 NSString 和 NSMutableString 的 Source Code。

在NSString.m中,看到了以下关于 copy 的方法。

- (id)copyWithZone:(NSZone *)zone {  
   if (NSStringClass == Nil)  
       NSStringClass = [NSString class];  
   return RETAIN(self);  
}  
- (id)mutableCopyWithZone:(NSZone*)zone {  
   return [[NSMutableString allocWithZone:zone] initWithString:self];  
}

而在 NSMutableString.m 中只发现了copyWithZone:和copy:方法,并且它调用了父类的全能初始化方法(designated initializer),所以构造出来的对象是由 NSString 持有的:

-(id)copy {  
   return [[NSString alloc] initWithString:self];  
}  
-(id)copyWithZone:(NSZone*)zone {  
   return [[NSString allocWithZone:zone] initWithString:self];  
}

也就是说, NSMutableString 进行 copy 的对象从源码上看也会变成深复制方法。我们做下试验。

Test 6

- (void)test6 {  
   NSMutableString *str = [NSMutableString stringWithFormat:@"iOS"];  
   NSLog(@"%p", str);  
   NSMutableString *str2 = [str copy];  
   NSLog(@"%p", str2);  
}
2016-08-12 15:12:12.845 TestCopy[73658:21549553] 0x7f96f8410e10   
2016-08-12 15:12:12.846 TestCopy[73658:21549553] 0xa00000000534f693

输出结果如我们所预料的,同样是 NSMutableString 之间的指针传递,即使类型相同,使用了该类型下的 copy 方法,也会变成深复制,因为返回的对象如源码所示,调用了 NSString 的全能初始化方法,并且由一个新的 NSString 持有。那么在 NSMutableString 中使用mutableCopy,可以做到单纯的 retain 操作吗。答案也是否定的,同样是源码中写道,在源码中并没有重写mutableCopy方法,也没有实现mutableCopyWithZone:方法,所以会调用父类的mutableCopyWithZone。而在父类中 mutableCopyWithZone:方法中调用了 NSMutableString 的全局初始化方法,所以依旧是深复制。

以上原则试用于大多数 Foundation 框架中的常用类,如 NSArray 、 NSDictionary 等等。

关于自定义 Class 的 Copy 方法

对于以上所有试验,我们可以总结一个中关系:

aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkJDQzA1MTVGNkE2MjExRTRBRjEzODVCM0Q0NEVFMjFBIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkJDQzA1MTYwNkE2MjExRTRBRjEzODVCM0Q0NEVFMjFBIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QkNDMDUxNUQ2QTYyMTFFNEFGMTM4NUIzRDQ0RUUyMUEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QkNDMDUxNUU2QTYyMTFFNEFGMTM4NUIzRDQ0RUUyMUEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz6p+a6fAAAAD0lEQVR42mJ89/Y1QIABAAWXAsgVS/hWAAAAAElFTkSuQmCC" alt="" data-ratio="0.3798283261802575" data-src="http://mmbiz.qpic.cn/mmbiz_jpg/z4icVKIQGzjEQW0AlPwf3nUeaqOO6VrA7aOHicBeOvGjVfIaErje8gzLe6dwhdnkTPibDCtRJKkqZicKgawuMCbcSQ/0?wx_fmt=jpeg" data-type="jpeg" data-w="932" />由此,我们可以总结一下。其实 Copy 对应的也就是类似于 immutableCopy 这种感觉,因为通过 copy 出的对象总是不可变的。所以对于一个 Class 中的 mutableCopy 和 copy 的方法命名而言,其实是否有 mutable ,是针对于返回值而言的。在 Foundation Framework 中,把拷贝方法称为 copy 而非 immutableCopy 的原因在于,NSCopying 这个基础协议不仅设计给那些具有可变和不可变版本的类来用,而且还要供其他一些没有“可变”和“不可变”之分的类来用。

所以在实现自定义 Class 的 Copy 方法适合,我们需要确定一个问题,应该执行深复制还是浅复制。然后在去实现对应的 copyWithZone: 和 mutableCopyWithZone: 两个方法。这里我不再多论,可以查看 Effective Objective-C 2.0 52 Specific Ways to Improve Your iOS and OS X Programes 的 Tips 22 。

对于很多博文的一些疑问

在很多关于讨论自定义 Class 中的 Copy 方法,都会强调一句:我们一定要遵循 NSCopying 或 NSMutableCopying 这两个协议,并且在实例代码中也显式写出了自定义的 Class 是遵循这两个协议的,如下:

@interface XXObject: NSObject<NSCopying, NSMutableCopying>   
@end

但是如果我们不显式的写出,我们发现不但没有 crash ,而且结果也是完全一样的。而在Objective-C copy那些事儿 此文中,作者写道:

正确的做法是让自定义类遵循NSCopying协议(NSObject并未遵循该协议)

我的猜想是,某次苹果所用的 Foundation 框架升级,使得 NSObject 开始遵循 NSCopying 方法,但是没有去实现(这就好比 c++ 中的 virtual 虚函数)。这里有待考证,如果有朋友知道,欢迎补充这一部分知识,请大家多多指教。


●本文编号107,以后想阅读这篇文章直接输入107即可。

●输入m可以获取到文章目录

从经典问题来看 Copy 方法(转)的更多相关文章

  1. 从经典问题来看 Copy 方法

    经典面试题:为什么 NSString 类型成员变量的修饰属性用 copy 而不是 strong (或 retain ) ? 在初学 iOS 的时候,可能会被灌输这么一个常识,切记 NSString 的 ...

  2. 可变与不可变类型数据,列表的copy方法

    我们先来了解一下可变与不可变类型的数据 (1)可变类型:列表,字典(内存中的数据允许被修改) 不可变类型:数字,字符串,元组(内存中的数据不允许被修改) 接着我们通过一个实例来看一看可变与不可变类型数 ...

  3. 【转载】C#的DataTable类Clone及Copy方法的区别

    在C#中的Datatable类中,Clone方法和Copy方法都可以用来复制当前的DataTable对象,但DataTable类中的Clone方法和Copy方法还是有区别的,Clone方法只复制结构信 ...

  4. python字典copy()方法

    python 字典的copy()方法表面看就是深copy啊,明显独立 d = {'a':1, 'b':2} c = d.copy() print('d=%s c=%s' % (d, c)) Code1 ...

  5. java中spring提供的属性copy方法

    BeanUtils.copyProperties(source, target); 今天用到属性的copy方法

  6. Python笔记·第六章——集合 (set) 的增删改查及 copy()方法

    简介: 集合是无序的,不重复的数据集合,它里面的元素是可哈希的(不可变类型),但是集合本身是不可哈希(所以集合做不了字典的键)的.以下是集合最重要的两点: 1.去重,把一个列表变成集合,就自动去重了. ...

  7. 由AbstractQueuedSynchronizer和ReentrantLock来看模版方法模式

    在学完volatile和CAS之后,近几天在撸AbstractQueuedSynchronizer(AQS)的源代码,很多并发工具都是基于AQS来实现的,这也是并发专家Doug Lea的初衷,通过写一 ...

  8. Python 字典 copy()方法

    描述 Python 字典 copy() 方法返回一个字典的浅拷贝(父不变,子变). 语法 copy() 方法语法: D.copy() 参数 无. 返回值 返回一个字典的浅拷贝(父不变,子变). 实例 ...

  9. Python3 列表 copy() 方法

    描述 Python3 列表 copy() 方法用于复制(浅拷贝)列表(父不变,子变),类似于 a[:]. 语法 copy() 方法语法: L.copy() 参数 无. 返回值 返回复制(浅拷贝)后的新 ...

随机推荐

  1. sudo: ./sd_fusing.sh:找不到命令

    1. -----

  2. java开发之IO流

    一直对IO流记不清楚,从别的地方转过来. 看下图: 流的概念和作用 学习Java IO,不得不提到的就是JavaIO流. 流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象.即数据在两 ...

  3. linux 入门教程

    linux入门教程 搜索 Linux入门教程 前言 第一章 关于Linux的历史 第二章 图形界面还是命令窗口 第三章 Linux操作系统的安装 第四章 初步进入linux世界 第五章 Linux系统 ...

  4. bzoj4028

    一眼分块题…… 分块,维护每个块的总的gcd和xor和 先思考我们应该怎么查询,考虑到gcd是一个神奇的东西,因为它最多变化logX次 于是我们从前往后扫描每个块,如果一个块内总的gcd是当前扫描的前 ...

  5. bzoj2150,poj1422,poj1548

    其实吧,bzoj2150还是比较水的, 在你知道什么是最小路径覆盖的前提下: 最小路径覆盖就是在有向无环图中,每个点只能被一条路径关联,问最少有多少条路能覆盖这个图 方法是,把对于原图每个点我们拆成左 ...

  6. RPi 2B UART作为调试口或者普通串口

    /************************************************************************************** * RPi 2B UAR ...

  7. 用VisualSVN做项目版本控制

    一.SVN服务端 1.VisualSVN Server下载: http://download.csdn.net/detail/jiminull/4448874 或 http://www.visuals ...

  8. Win10 查看IE的临时目录

    C:\Users\YOUR_NAME\AppData\Local\Microsoft\Windows\INetCache

  9. PHP基本语法的小结

    一.PHP能做什么? PHP能做什么?我觉得它很强大,只要我能想到的,它都能做,只是我技术能力还不行╮(╯﹏╰)╭.好吧,一张图,基本了解一下吧(ps:PHP的功能不局限于此( ^_^ )) 图像有点 ...

  10. python 网络编程(四)---UDP服务端客户端

    1.服务器端 UDP服务器建立与TCP相类似,具体比较如下: 补充下,第四步:不必使用listen还有accept函数. 具体代码如下:(设置socket选项省略) import socket fro ...