![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=1738912083-9nDjLwAVYDMxXCZzohQJ5b7Z2G6JuQKB-0-ac5cd2b702935579bc6079c1b9dd74c2)
- 在main.m文件中添加如下代码,在代码中首先创建了一个字符串对象string以及一个ClassA类的对象classA,并且把该字符串对象赋值给classA对象的name属性,然后对string对象的值进行修改,最后打印两个字符串对象存储的内存地址。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T148_17633.jpg?sign=1738912083-dGOYjzqBu2Ug0rFNe1ggcyOsNh6NgaXU-0-2c43576a11782ac3a2b11a7552cf95d8)
- 运行结果如图6-1所示,可以看到两个字符串存储的内存地址不同,当修改其中一个字符串时,另外一个字符串是不会发生改变的。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P149_17734.jpg?sign=1738912083-Fm4FWGN04iniredgOExMXuSv0HeVdRUn-0-ba01e920122d40c8c263ddf7f1cd1067)
图6-1 运行结果
- 接下来,修改属性关键字为strong,如下所示:
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T149_17738.jpg?sign=1738912083-QVycI0tg1EGLGrJtN2iPEACSSVXmogne-0-a91bb4b3b69b2c53a50f107f0e8abcfc)
- 再次运行后,运行结果如图6-2所示。可以看到两个字符串指针指向同一个内存地址。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P149_17740.jpg?sign=1738912083-VkHhoOzJxLrqJcj2gCFoahX8t97xcKCC-0-23935944d909b26108cce6b7331d9db1)
图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=1738912083-HEq5U1D22PWHSqBlyQkQmy1E2hGc9ApU-0-de374cecf1e9e67229f15912d84e762b)
- 当对象调用mutableCopy方法时,会返回NSCopying协议中mutableCopyWithZone:方法的返回结果,前提是对象在定义中遵守了NSCopying协议。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T150_17845.jpg?sign=1738912083-QGKIQcwRVNF2AyRqWworIZrdiZyyqwoD-0-a0f07d730c3d2c6d034bc86045fe603a)
当对不同类型的对象分别使用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=1738912083-LfLAc4eKDqbccD6yfL7ch5Qk2REdqNEp-0-1621c45fa9d7947912775c6e36398ec7)
运行结果如图6-3所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P150_17849.jpg?sign=1738912083-CuWssTP3jq3zeEv2FoMycsXGI3TMwJ23-0-a55093d20b269e3392d5fc90b1683879)
图6-3 运行结果
4.NSMutableString对象复制
NSMutableString对象的复制属于非容器类且可变对象的复制。通过下方的示例代码验证后,可以得到如下结论:NSMutableString类使用copy或者mutableCopy均为深复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T151_17994.jpg?sign=1738912083-I2816nQnNXVpOhJeNWj9VHWTcvLU76T6-0-b57aee5dd4d066253c14b22a9f905b9b)
运行结果如图6-4所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P151_17996.jpg?sign=1738912083-gjTHDNoNg2NqXd7UGgm7ogHTNz9AD0uZ-0-c8aabdcbc25a8f222486674808bb85ea)
图6-4 运行结果
5.NSArray对象的复制
NSArray对象的复制属于容器类且不可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSArray类使用copy为浅复制,使用mutableCopy为深复制,另外,不论使用copy还是mutableCopy,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T151_18000.jpg?sign=1738912083-H8NlrkSJyRcdH5QjHT3iSRaSM6anOCal-0-4cf47c87133b43f83ad870cdc6bdc747)
运行结果如图6-5所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P152_18144.jpg?sign=1738912083-XbZbzgQZ8wOzxgru86dnHRnchNiLjDUA-0-1f152fdafec0d580d961d5d42d82abe6)
图6-5 运行结果
6.NSMutableArray对象复制
NSMutableArray对象复制属于容器类且可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSMutableArray类不论使用copy还是mutableCopy都为深复制,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T152_18148.jpg?sign=1738912083-U9eByBhHMY3oRhN3JLxnLT5xD8EntwPq-0-7f822cd0450da0a5717faa64ff950e90)
运行结果如图6-6所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P153_18288.jpg?sign=1738912083-bVI1epvLRNzXuzLmScyz2bUZmRhm7OsA-0-c77dbcd1a3e476a006338f008b08358f)
图6-6 运行结果
7.NSDictionary对象复制
NSDictionary对象的复制属于容器类且不可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSDictionary类使用copy为浅复制,使用mutableCopy为深复制,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T153_82857.jpg?sign=1738912083-NaS5NNeXJ5nplE7qW8StIwzo7g30ts4g-0-4d55f732129a5f8fce3a24e27a9ac2a1)
运行结果如图6-7所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P154_18415.jpg?sign=1738912083-g2TZ8kI2JxEhLy1vZpKKikiBPjovxR0g-0-a59961cbd4982492b1dbc9a8114f8242)
图6-7 运行结果
8.NSMutableDictionary对象复制
NSMutableDictionary对象复制属于容器类且可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSMutableDictionary类不论使用copy还是mutableCopy均为深复制,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T154_18419.jpg?sign=1738912083-n9N97wp6PeDcRzi16guNCxTjucNltipS-0-b969c333c6b6dda8b41db88b89ec1098)
运行结果如图6-8所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P154_18421.jpg?sign=1738912083-CY7wMMQryNVc6OmLEhGawyzHJDpT01ot-0-ee6ea78ab04ab4baf5bbd833ce1f1d47)
图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=1738912083-ujJXrGEidZUqVLpKlnY8CawZ2iMQyuaH-0-e9780c92614235666c8da713bd818cb1)
为了实现对该类对象的复制,要求ClassB类遵守NSCopying协议,同时在类的.m文件中实现copyWithZone:方法,在该方法中的实现逻辑决定了当调用copy方法时,对该类对象进行复制所采取的方式(深复制或者浅复制)。
2.浅复制该类的对象
当仅仅需要对该对象进行浅复制时,可以在copyWithZone:方法中,直接返回要复制的对象即可。
- 在ClassB.m文件中,实现copyWithZone:方法。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T155_18529.jpg?sign=1738912083-izJh7U1xbF2qpxDwCqNDREumcHsG6q03-0-dc62d77efbdcef6c758b2042030ebe58)
3.深复制该类的对象
当需要对自定义对象深复制时,需要在copyWithZone:方法中调用alloc以及init方法,重新开辟一块新的内存空间。另外,对于属性的复制过程中,也需要考虑到属性自身的特性,例如:有copy特性的属性需要重新生成新的副本,strong以及weak只需要做指针赋值即可。
- 在ClassB.m文件中,实现copyWithZone:方法。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T155_82858.jpg?sign=1738912083-8ZZhG2yn1AZgiBDwldVsVNFviWrATTM6-0-e9d4f2d03b60603a0e1d03b0775aaa37)
4.深复制与浅复制代码验证
在main.m文件中,添加一个函数,用来复制ClassB的对象,代码如下所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T156_18643.jpg?sign=1738912083-KSVrE0RSiOCqvZBbhwjUdKEtsU9GEOMp-0-9f511b0099bf57cf4a77d76465c298a8)
当ClassB中的copyWithZone:方法中采用浅复制时,运行结果如图6-9所示。复制后得到的副本指向同一内存地址,即进行了指针复制,内容还是原来的内容。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P156_18645.jpg?sign=1738912083-FyvRu7I9M2sn80gPcycSkz7mRZDeWRJo-0-713e5e3d0f9b206fc4738e94034ab9f2)
图6-9 运行结果
当ClassB中的copyWithZone:方法中采用深复制时,运行结果如图6-10所示。可以看到复制得到的对象与原对象的地址不同,同时属性中包含copy关键字的属性在复制过程中也进行了深复制,而strong/weak特性的属性仅仅做了指针复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P156_18649.jpg?sign=1738912083-84KPR51pDTzhWXmnVpQu5uuKb8S44nDE-0-c01be586469099f0c9abedd85fc447e1)
图6-10 运行结果