![iOS开发:从零基础到精通](https://wfqqreader-1252317822.image.myqcloud.com/cover/796/26793796/b_26793796.jpg)
第6章 Objective-C进阶
6.1 对象复制
6.1.1 浅复制与深复制
1.浅复制与深复制的简介
在Objective-C中,基本数据类型(例如int、float、BOOL等)的复制比较简单,都是会在内存中对需要赋值的变量创建一个副本,而对象的复制有两种形式:浅复制与深复制。
- 浅复制:将原始对象的指针值复制到副本中,即指针复制,原始对象和副本共享引用的数据,相当于创建了一个文件的快捷方式。
- 深复制:复制原始对象指针所引用的数据,并将其赋给副本对象,即内容复制,相当于创建了一份新的文件。
当为一个类的属性添加copy关键字时,那么对这个属性赋值时(即调用setter方法),就会执行深复制操作,同时还需要该类遵守NSCopying协议。当把属性关键字改为strong或者weak时,那么对这个属性赋值时,就会执行浅复制(只复制指针地址)。
2.示例代码
下方的示例代码中,通过修改一个属性的关键字copy/strong来学习一下有关对象复制的两种方式。
- 新增一个ClassA类,添加一个NSString类型的name属性,并添加copy关键字。另外,NSString类已经遵守了NSCopying协议。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T148_17631.jpg?sign=1739331111-n7amoQwQgUvS3rgkbJzdvqEVF4Aj1mgr-0-9dc14c5f5b39be707cc5ca1e6baaee91)
- 在main.m文件中添加如下代码,在代码中首先创建了一个字符串对象string以及一个ClassA类的对象classA,并且把该字符串对象赋值给classA对象的name属性,然后对string对象的值进行修改,最后打印两个字符串对象存储的内存地址。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T148_17633.jpg?sign=1739331111-svx6OapHkDjpqeG2zLDZIszbZHkViRu9-0-778f721d72a21572f2a5fba3d761ec0c)
- 运行结果如图6-1所示,可以看到两个字符串存储的内存地址不同,当修改其中一个字符串时,另外一个字符串是不会发生改变的。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P149_17734.jpg?sign=1739331111-JZk9nt9FHbSInuzYmkwMOxkxA2T7BeZe-0-ff0041ba21002a5e5ab0f1b66162e4a2)
图6-1 运行结果
- 接下来,修改属性关键字为strong,如下所示:
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T149_17738.jpg?sign=1739331111-JFlATz1icSeCBZHCSRS7VvVrqIYqRAU8-0-f5b5aa1bb7eef10cb0808e6b591882b9)
- 再次运行后,运行结果如图6-2所示。可以看到两个字符串指针指向同一个内存地址。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P149_17740.jpg?sign=1739331111-vHWCwoSI3K8m1lwumfcjhvVTbMX8763i-0-6a4b4d9b4a915100ce6e37ccfdb7bcd7)
图6-2 运行结果
6.1.2 可变对象复制与不可变对象复制
在Foundation框架中,常用的几个类,如NSString、NSArray以及NSDictionary都有其对应的可变子类。当对不同类的对象进行复制时,系统会采用不同的复制方式,有的采用浅复制,有的采用深复制,因此有必要提前了解对不同类型的对象进行复制时,是指针复制还是值复制。
1.复制操作(copy与mutableCopy方法)
在NSObject类中提供了两种复制的实例方法,copy和mutableCopy。
- 当对象调用copy方法时,会返回NSCopying协议中的copyWithZone:方法的返回结果,前提是对象在定义中遵守了NSCopying协议。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T149_17745.jpg?sign=1739331111-JKpnePpWTdM0Oz1OYf9kvUoPdOxpYpru-0-413d20aab564656046709d22b10ac662)
- 当对象调用mutableCopy方法时,会返回NSCopying协议中mutableCopyWithZone:方法的返回结果,前提是对象在定义中遵守了NSCopying协议。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T150_17845.jpg?sign=1739331111-M9H27Dddjc0BbDXzu4ywQza6tM05LNZS-0-a7fc62710dd576c4bfc9d50837196e46)
当对不同类型的对象分别使用copy和mutableCopy方法进行复制时,可能对应不同的复制类型(深复制或浅复制),这取决于类中copyWithZone:以及mutableCopyWithZone:方法的实现逻辑。
2.可变类与不可变类以及容器类与非容器类
在Foundation框架中,常用的几个不可变的类,如NSString、NSArray,NSDictionary都有对应的可变子类(NSMutableString、NSMutableArray、NSMutableDictionary)。不可变的类实例化后的对象,分配的内存空间不能再变化,而可变类实例化后的对象,分配的内存可以动态变化。因此,可以修改一个可变字符串的内容,或者在一个可变数组中新增/删除其中的对象。
容器类就是该类的对象可以用来容纳其他对象,最典型的是数组NSArray以及NSMutableArray。非容器类的对象不能够容纳其他对象,例如,字符串。
可变类/不可变类与容器类/非容器类进行分类组合就构成了四种情况:容器类不可变对象,容器类可变对象,非容器类不可变对象以及非容器类可变对象。这四种组合进行复制时,得到的复制对象会有所区别,需要程序员注意。
3.NSString对象复制
NSString对象的复制属于非容器类不可变对象的复制。通过下方的示例代码验证后,可以得到如下结论:NSString类使用copy为浅复制(指针复制),使用mutableCopy为深复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T150_17847.jpg?sign=1739331111-wKQdaMAcXliDuRJUQnV674iz8yzAk62n-0-462ca3c9e116e14caab634153031352f)
运行结果如图6-3所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P150_17849.jpg?sign=1739331111-mg1DSTjM7gBRv0a5Ki5I3lVJvHZfJBOh-0-e7a573116b64b4ac6d3501e4218bf1cb)
图6-3 运行结果
4.NSMutableString对象复制
NSMutableString对象的复制属于非容器类且可变对象的复制。通过下方的示例代码验证后,可以得到如下结论:NSMutableString类使用copy或者mutableCopy均为深复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T151_17994.jpg?sign=1739331111-hAxOO4gglT48lGLNU6xrHyz0lTXWkWOz-0-8ac524556fdbe171358cdc938f8a339f)
运行结果如图6-4所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P151_17996.jpg?sign=1739331111-ZYxymInUqhdYIfAVboagLcw1ltqPgRPa-0-fdfdd3eb947ed9c8fe49d7ac4daca3cf)
图6-4 运行结果
5.NSArray对象的复制
NSArray对象的复制属于容器类且不可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSArray类使用copy为浅复制,使用mutableCopy为深复制,另外,不论使用copy还是mutableCopy,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T151_18000.jpg?sign=1739331111-tSH6pNuhfnRxp8e2B8hZlVDdgBhhPquI-0-995a8288bd482a9fa43bfa80c20b64a3)
运行结果如图6-5所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P152_18144.jpg?sign=1739331111-YDeFW66M5ap8BbPDiF4bdowYlJ64Onxe-0-e523e46177356af8cf7979275be3f4f1)
图6-5 运行结果
6.NSMutableArray对象复制
NSMutableArray对象复制属于容器类且可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSMutableArray类不论使用copy还是mutableCopy都为深复制,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T152_18148.jpg?sign=1739331111-HCL6t7LIaq32Yf7ImFJbjxqUcdXErj2N-0-78b384cb20ffdf4996aef979264561eb)
运行结果如图6-6所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P153_18288.jpg?sign=1739331111-RLN140iMfIs1dJX7wVvX5vMVbq2nDauu-0-e66c71d5fa70f1c15b59141b8e854b0e)
图6-6 运行结果
7.NSDictionary对象复制
NSDictionary对象的复制属于容器类且不可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSDictionary类使用copy为浅复制,使用mutableCopy为深复制,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T153_82857.jpg?sign=1739331111-XTHXGeeZChS39bdaHSGHH763Z2Agylrb-0-c836950529762aed099ac5c41f8d49d9)
运行结果如图6-7所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P154_18415.jpg?sign=1739331111-Z6mxQYaTmupvtGLk8T4CelVKIQwHorQ2-0-06327cfb4f4e0c2be18e9143f522203d)
图6-7 运行结果
8.NSMutableDictionary对象复制
NSMutableDictionary对象复制属于容器类且可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSMutableDictionary类不论使用copy还是mutableCopy均为深复制,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T154_18419.jpg?sign=1739331111-2eVNMehlLUKwRuv8p1n1nXoa6ml4OWl8-0-8e18a3eeab543a43e0e1c6686af64bf1)
运行结果如图6-8所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P154_18421.jpg?sign=1739331111-DZZW6nyUjxAlh1FHq0DuXEsLno64Yr1y-0-0103148579cc5970caa092bd17b9c986)
图6-8 运行结果
6.1.3 自定义对象复制
在实际开发中,对于一些自定义的对象,有时也希望对其进行复制。对于自定义对象的复制,首先要保证在类的定义中遵守NSCopying协议,然后实现copyWithZone:方法,对于自定义对象的复制特性(浅复制或深复制),都取决于copyWithZone:方法中的实现情况,对于类中定义的属性也需要综合考虑其定义中有关内存管理的特性(strong/weak/copy/assign)。
1.类的定义与复制
首先自定义ClassA类以及ClassB类,并在ClassB类中,添加4个属性,这4个属性分别使用了copy、strong、weak和assign关键字,如下所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T155_18527.jpg?sign=1739331111-EnF1YdYzu9ABA7ZmX4kZmGGT9PQLeToW-0-2aead17f19bb64d145234442e3e3f9df)
为了实现对该类对象的复制,要求ClassB类遵守NSCopying协议,同时在类的.m文件中实现copyWithZone:方法,在该方法中的实现逻辑决定了当调用copy方法时,对该类对象进行复制所采取的方式(深复制或者浅复制)。
2.浅复制该类的对象
当仅仅需要对该对象进行浅复制时,可以在copyWithZone:方法中,直接返回要复制的对象即可。
- 在ClassB.m文件中,实现copyWithZone:方法。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T155_18529.jpg?sign=1739331111-wzzjStON5gndOW2EvWDDuyoINy20rLkq-0-4293b3f4d17eb7b230b03c72616442d6)
3.深复制该类的对象
当需要对自定义对象深复制时,需要在copyWithZone:方法中调用alloc以及init方法,重新开辟一块新的内存空间。另外,对于属性的复制过程中,也需要考虑到属性自身的特性,例如:有copy特性的属性需要重新生成新的副本,strong以及weak只需要做指针赋值即可。
- 在ClassB.m文件中,实现copyWithZone:方法。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T155_82858.jpg?sign=1739331111-aaGgDoaup4dpv9KiI9EhHUmW8SsylmSZ-0-90776559a7ea9597a5fb6c020d4fdd9f)
4.深复制与浅复制代码验证
在main.m文件中,添加一个函数,用来复制ClassB的对象,代码如下所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T156_18643.jpg?sign=1739331111-Hn9XKagfMLVlCu4BibqPWlyZLbHFKF0S-0-f99196554732c8b8b6eb8564f1bbba9c)
当ClassB中的copyWithZone:方法中采用浅复制时,运行结果如图6-9所示。复制后得到的副本指向同一内存地址,即进行了指针复制,内容还是原来的内容。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P156_18645.jpg?sign=1739331111-yEhFvRHWjhZEGQVFWGmIpm2yPHsKlgv8-0-fdaa2b52a4f62a281dc22150b42186d9)
图6-9 运行结果
当ClassB中的copyWithZone:方法中采用深复制时,运行结果如图6-10所示。可以看到复制得到的对象与原对象的地址不同,同时属性中包含copy关键字的属性在复制过程中也进行了深复制,而strong/weak特性的属性仅仅做了指针复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P156_18649.jpg?sign=1739331111-e70r0zAVSG099KGUhjRmSboD0Vw2d2vx-0-e8c016c1d2aabb9cd6169598dd6e1954)
图6-10 运行结果