![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=1739329754-x3Ul21lE78X0eOGknCa3zK5WmUNeGMSI-0-3cb06935c1f6fb734ff4b8626f8ef1cd)
- 在main.m文件中添加如下代码,在代码中首先创建了一个字符串对象string以及一个ClassA类的对象classA,并且把该字符串对象赋值给classA对象的name属性,然后对string对象的值进行修改,最后打印两个字符串对象存储的内存地址。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T148_17633.jpg?sign=1739329754-KppO4kNtdNm56JQochVtLukENYMxZxNz-0-15426dddc289f524dd7ee714b869ca10)
- 运行结果如图6-1所示,可以看到两个字符串存储的内存地址不同,当修改其中一个字符串时,另外一个字符串是不会发生改变的。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P149_17734.jpg?sign=1739329754-1tUM80kpxV0uwtusYVvlevcximBEL1Gb-0-9ce91b2e65beb21bb4249c936f3c1447)
图6-1 运行结果
- 接下来,修改属性关键字为strong,如下所示:
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T149_17738.jpg?sign=1739329754-VzEesAf0ODAbcrmVJZkAzhQYvXqWPwyw-0-8d981085f25d94538a53faff03cef15b)
- 再次运行后,运行结果如图6-2所示。可以看到两个字符串指针指向同一个内存地址。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P149_17740.jpg?sign=1739329754-EkAPFU03UdQ1Kt4hXL7ogi6Ji921iDaM-0-758196797f6a2ab1397b44a1b0541f22)
图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=1739329754-Usaety3UDCiiWWRj6wA2XXnNsistruCV-0-8e490161bf0780d4d5d9c69219793bdc)
- 当对象调用mutableCopy方法时,会返回NSCopying协议中mutableCopyWithZone:方法的返回结果,前提是对象在定义中遵守了NSCopying协议。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T150_17845.jpg?sign=1739329754-mYbaiM4e6WqQV61ybkzwCz0NKpK4uo6U-0-3b61f26a6267425eb26d66d2ed900cf7)
当对不同类型的对象分别使用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=1739329754-OckFoabp2aqBI7jSjxfAglJP7lqBQyFd-0-844de02090a31d3829766a191c8f08ee)
运行结果如图6-3所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P150_17849.jpg?sign=1739329754-w3sU0eH3Lond0Lmj45biFU6iAPVjlFRN-0-090583fcc7b4caf749d700e41459c9a9)
图6-3 运行结果
4.NSMutableString对象复制
NSMutableString对象的复制属于非容器类且可变对象的复制。通过下方的示例代码验证后,可以得到如下结论:NSMutableString类使用copy或者mutableCopy均为深复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T151_17994.jpg?sign=1739329754-IpAIidh5C4fbjTaZ5EvKWXU83D2QpiFH-0-b2a1ac73e88eae2df9aedd8f1e473ec6)
运行结果如图6-4所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P151_17996.jpg?sign=1739329754-6N28imK6CvEsTcaw2wT3eiZ13mk5z8v9-0-6b466ed3801535cb1d88da1161e21538)
图6-4 运行结果
5.NSArray对象的复制
NSArray对象的复制属于容器类且不可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSArray类使用copy为浅复制,使用mutableCopy为深复制,另外,不论使用copy还是mutableCopy,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T151_18000.jpg?sign=1739329754-wPcIsCSijz2nT3VHAoSRJkSHC0Atnvxi-0-caa3f07ada5b1991f71f3a02bac725e1)
运行结果如图6-5所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P152_18144.jpg?sign=1739329754-V6Mvxu9Qj3c8C31rvj4cPY0gpc8tXLzA-0-fb0d5573a940c676953da29a29703d30)
图6-5 运行结果
6.NSMutableArray对象复制
NSMutableArray对象复制属于容器类且可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSMutableArray类不论使用copy还是mutableCopy都为深复制,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T152_18148.jpg?sign=1739329754-FSa4ukcQDDtnNxtedYUdJm2nUHsIbZGi-0-b49e86b95780d9c337dcf5031b9a8d20)
运行结果如图6-6所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P153_18288.jpg?sign=1739329754-6RxKYQf1SuQa12JJNDCwNsIB1ZbgvtqB-0-63aafb79cf88f61e4edad89599562497)
图6-6 运行结果
7.NSDictionary对象复制
NSDictionary对象的复制属于容器类且不可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSDictionary类使用copy为浅复制,使用mutableCopy为深复制,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T153_82857.jpg?sign=1739329754-lFvFvwTR5AAdD3rhfXXryIZkMEewEZzS-0-999593e9c3df226cd3792d3fc3852a6a)
运行结果如图6-7所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P154_18415.jpg?sign=1739329754-sj0YZ2t5XsjY6kGHtU4S0bgx487KYwuj-0-be28a3c0709655c0da87711939780f38)
图6-7 运行结果
8.NSMutableDictionary对象复制
NSMutableDictionary对象复制属于容器类且可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSMutableDictionary类不论使用copy还是mutableCopy均为深复制,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T154_18419.jpg?sign=1739329754-ZzpZfMZqQ0Ich00P3ta6aOBMfVYM72Nt-0-ac0a5be65d66daf2c311d04eb0f563be)
运行结果如图6-8所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P154_18421.jpg?sign=1739329754-ss3vHWCIsLiVKMgmOEW6kduQZZezWtyL-0-730876650e3a1354190b5637f5d55d1a)
图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=1739329754-pD1vLopwccXPXG75B4suiBuVT5noxfar-0-2b363a1993277a4d7907f24ad34c26e4)
为了实现对该类对象的复制,要求ClassB类遵守NSCopying协议,同时在类的.m文件中实现copyWithZone:方法,在该方法中的实现逻辑决定了当调用copy方法时,对该类对象进行复制所采取的方式(深复制或者浅复制)。
2.浅复制该类的对象
当仅仅需要对该对象进行浅复制时,可以在copyWithZone:方法中,直接返回要复制的对象即可。
- 在ClassB.m文件中,实现copyWithZone:方法。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T155_18529.jpg?sign=1739329754-78uhcdRmXOW2xoinllJT5ibirN29CiLu-0-cc33b1e1767ad36cfb5defaede12a93d)
3.深复制该类的对象
当需要对自定义对象深复制时,需要在copyWithZone:方法中调用alloc以及init方法,重新开辟一块新的内存空间。另外,对于属性的复制过程中,也需要考虑到属性自身的特性,例如:有copy特性的属性需要重新生成新的副本,strong以及weak只需要做指针赋值即可。
- 在ClassB.m文件中,实现copyWithZone:方法。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T155_82858.jpg?sign=1739329754-KB9H5OaFCBZqSbjMnt9sPk6KNMVlv3dM-0-dd6179d1b540ac902b3c8f25f2d33d3a)
4.深复制与浅复制代码验证
在main.m文件中,添加一个函数,用来复制ClassB的对象,代码如下所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T156_18643.jpg?sign=1739329754-84zxlHrBI47h2CBFikL444kuwmE9qGBE-0-4cd0e073995354c476b22317fd5bf07f)
当ClassB中的copyWithZone:方法中采用浅复制时,运行结果如图6-9所示。复制后得到的副本指向同一内存地址,即进行了指针复制,内容还是原来的内容。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P156_18645.jpg?sign=1739329754-yrgVTuXHj1W13GB2nLom631RIV9Q5ulo-0-278c3f4d60251fac337543b168602e49)
图6-9 运行结果
当ClassB中的copyWithZone:方法中采用深复制时,运行结果如图6-10所示。可以看到复制得到的对象与原对象的地址不同,同时属性中包含copy关键字的属性在复制过程中也进行了深复制,而strong/weak特性的属性仅仅做了指针复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P156_18649.jpg?sign=1739329754-exjcT9dwIFmVe9o0bIIbb2j1nOQkzSkF-0-39be40986faccfcbec7acb3a34f3b0b7)
图6-10 运行结果