![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=1739329403-4goYOR9BGSvHE5TQNKs1MiqVFLWSju3M-0-9049d17f9da465760b65cfee113ee9c3)
- 在main.m文件中添加如下代码,在代码中首先创建了一个字符串对象string以及一个ClassA类的对象classA,并且把该字符串对象赋值给classA对象的name属性,然后对string对象的值进行修改,最后打印两个字符串对象存储的内存地址。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T148_17633.jpg?sign=1739329403-2QChFVXDIbdGDJhj4pjBZ5DKB1l2mtqQ-0-8c441b8417ef4f6d15f3423385d8ff0c)
- 运行结果如图6-1所示,可以看到两个字符串存储的内存地址不同,当修改其中一个字符串时,另外一个字符串是不会发生改变的。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P149_17734.jpg?sign=1739329403-0gNpYcKBNlhfJGVCtoPJpRrGoM1r1GH7-0-7779dee90b37487bf85c241c87b81a69)
图6-1 运行结果
- 接下来,修改属性关键字为strong,如下所示:
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T149_17738.jpg?sign=1739329403-YV8GK0jlEmupESGjAKNwg19IGPKBOXRv-0-1add693b25b0b758fd37c5f000e53717)
- 再次运行后,运行结果如图6-2所示。可以看到两个字符串指针指向同一个内存地址。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P149_17740.jpg?sign=1739329403-gvjZeuAYwmbN5iOIbEeuyf2Cb9iejOde-0-e1dfb82748553329da56728d44fedb33)
图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=1739329403-1aC3bmZI6jw5MmCNq1s7u10lbltQ6U4B-0-6246ff313f7fa77dfcc983676d16dd53)
- 当对象调用mutableCopy方法时,会返回NSCopying协议中mutableCopyWithZone:方法的返回结果,前提是对象在定义中遵守了NSCopying协议。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T150_17845.jpg?sign=1739329403-eNH6Zl4VdvbAUfhiUu3UjatnXsoHAASU-0-97d70e6776d89e9cb000e1023bbaff92)
当对不同类型的对象分别使用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=1739329403-vr5pYfvb0fcByFiya3jW8briqjo7CaIU-0-fb30c9e7e0c4d71e314e6d25e618f7ac)
运行结果如图6-3所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P150_17849.jpg?sign=1739329403-Ua4JPmIgyjBmxKr4CZsjtF1RFBiJJUOi-0-21684edb6a17f3459695209a66f157f5)
图6-3 运行结果
4.NSMutableString对象复制
NSMutableString对象的复制属于非容器类且可变对象的复制。通过下方的示例代码验证后,可以得到如下结论:NSMutableString类使用copy或者mutableCopy均为深复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T151_17994.jpg?sign=1739329403-ronp1pRQkyai7Y2xDEptJyFCl85ARvG4-0-aec50f663f0b12dd8d54066088d77bb7)
运行结果如图6-4所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P151_17996.jpg?sign=1739329403-GY5EOVw9pDXq3jgeCfW6GdPsiENb0RCb-0-38fc09d4b26a88aebd4912cc372ac77d)
图6-4 运行结果
5.NSArray对象的复制
NSArray对象的复制属于容器类且不可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSArray类使用copy为浅复制,使用mutableCopy为深复制,另外,不论使用copy还是mutableCopy,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T151_18000.jpg?sign=1739329403-gKbq6kfkaGZewOjh7CRIPhKymgetY9Un-0-331c9fb2ab8c808341114c7ef72e7771)
运行结果如图6-5所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P152_18144.jpg?sign=1739329403-Bz8EaYKWHCBIOSsFNBeq1tnkdj9y2PlU-0-b66ce65675db1b78dba8556ab13ddf56)
图6-5 运行结果
6.NSMutableArray对象复制
NSMutableArray对象复制属于容器类且可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSMutableArray类不论使用copy还是mutableCopy都为深复制,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T152_18148.jpg?sign=1739329403-GWzb9cEISSi0qirAQos9sqErDbL928UH-0-859895c49748053df920c814f095d586)
运行结果如图6-6所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P153_18288.jpg?sign=1739329403-17M8gQENRyKUXQM0Sit1OT6Ush1sIv6I-0-84413a1b4e47e444d5b2020f5be11fda)
图6-6 运行结果
7.NSDictionary对象复制
NSDictionary对象的复制属于容器类且不可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSDictionary类使用copy为浅复制,使用mutableCopy为深复制,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T153_82857.jpg?sign=1739329403-r7OuhWnhk2U1H6bvVS7zyU4mf1ps5k0d-0-056029e01864017de5bcfb11307ef182)
运行结果如图6-7所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P154_18415.jpg?sign=1739329403-rfAd2nXrOKiNdfY9GbaJtJW4x53DDfqG-0-104fa7ab4fb372ac926c408e971c28b9)
图6-7 运行结果
8.NSMutableDictionary对象复制
NSMutableDictionary对象复制属于容器类且可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSMutableDictionary类不论使用copy还是mutableCopy均为深复制,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T154_18419.jpg?sign=1739329403-AipPJ8MZCi5Zdfv78WxAnYWHaS5vwm8j-0-61984a2ce4d6aa51c41f16d831d62085)
运行结果如图6-8所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P154_18421.jpg?sign=1739329403-rxOOu7IylIInhIMEZfwtajKUGaXDLHGx-0-15acb759dd976d7555c30c1a68c32eb4)
图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=1739329403-JlT0rvoDSldIFguxf3Bgdpxi39rmPxRg-0-d0e7cb09125c58b84d52c49420b2355a)
为了实现对该类对象的复制,要求ClassB类遵守NSCopying协议,同时在类的.m文件中实现copyWithZone:方法,在该方法中的实现逻辑决定了当调用copy方法时,对该类对象进行复制所采取的方式(深复制或者浅复制)。
2.浅复制该类的对象
当仅仅需要对该对象进行浅复制时,可以在copyWithZone:方法中,直接返回要复制的对象即可。
- 在ClassB.m文件中,实现copyWithZone:方法。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T155_18529.jpg?sign=1739329403-henDNaxvgIzFaCI9A4yqMNy84Xc3dOvN-0-ea4b60242bc7ebaf082ec05129e19b6c)
3.深复制该类的对象
当需要对自定义对象深复制时,需要在copyWithZone:方法中调用alloc以及init方法,重新开辟一块新的内存空间。另外,对于属性的复制过程中,也需要考虑到属性自身的特性,例如:有copy特性的属性需要重新生成新的副本,strong以及weak只需要做指针赋值即可。
- 在ClassB.m文件中,实现copyWithZone:方法。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T155_82858.jpg?sign=1739329403-s0P1BPf7D4aqfBEXJ0LEHgory1Y6zF0R-0-eb6cea519f7ba9c7b3188aa29099389f)
4.深复制与浅复制代码验证
在main.m文件中,添加一个函数,用来复制ClassB的对象,代码如下所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T156_18643.jpg?sign=1739329403-pXBhJoysxOt2xlw4dxoPRLzCrBHuOWr7-0-b92ca062560f791210065a9a8919aeda)
当ClassB中的copyWithZone:方法中采用浅复制时,运行结果如图6-9所示。复制后得到的副本指向同一内存地址,即进行了指针复制,内容还是原来的内容。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P156_18645.jpg?sign=1739329403-EvMIlcToCjUkFVNE8Z8STOdTtXQdN75v-0-e9f8a147c90009e291d22edafa9d7044)
图6-9 运行结果
当ClassB中的copyWithZone:方法中采用深复制时,运行结果如图6-10所示。可以看到复制得到的对象与原对象的地址不同,同时属性中包含copy关键字的属性在复制过程中也进行了深复制,而strong/weak特性的属性仅仅做了指针复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P156_18649.jpg?sign=1739329403-nsxoXj2Jib4NCwbfp7UVqTSipx9iFLtk-0-09192a2cd224b0693c5d4f1bf4efdca9)
图6-10 运行结果