![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=1739545986-HOT63LtjyYspu86j2uGWvpKluVSBidpJ-0-b49cf7673d9c6cef59f54f14e3b1db70)
- 在main.m文件中添加如下代码,在代码中首先创建了一个字符串对象string以及一个ClassA类的对象classA,并且把该字符串对象赋值给classA对象的name属性,然后对string对象的值进行修改,最后打印两个字符串对象存储的内存地址。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T148_17633.jpg?sign=1739545986-3WwMIfxzVqkHnxhLimisHPizXmUPRba1-0-152c6bdbea7f8ea15c193f4e94bbf886)
- 运行结果如图6-1所示,可以看到两个字符串存储的内存地址不同,当修改其中一个字符串时,另外一个字符串是不会发生改变的。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P149_17734.jpg?sign=1739545986-XIB44s0jTPJHRfeNeGv9SEwuIF99z6zw-0-cdf97a44c6159aaa0f8ebedfb7e0bb93)
图6-1 运行结果
- 接下来,修改属性关键字为strong,如下所示:
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T149_17738.jpg?sign=1739545986-gMnC4BrQMWmZ8ihRLvnLVcBOLbm3ygda-0-f0b3faa9c5ad3f15bb71dec7e9ceadae)
- 再次运行后,运行结果如图6-2所示。可以看到两个字符串指针指向同一个内存地址。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P149_17740.jpg?sign=1739545986-KD4gfRGLyVaEHVYLfohOeH5yvV7Hg02v-0-9ac48ab6a072209a110bc1dd3887f11b)
图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=1739545986-718qHXEBCvAdprQh8L6wMBJCJvtc6XkV-0-a9da88bb918b46db37993dd37f0fc7a2)
- 当对象调用mutableCopy方法时,会返回NSCopying协议中mutableCopyWithZone:方法的返回结果,前提是对象在定义中遵守了NSCopying协议。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T150_17845.jpg?sign=1739545986-liqdlsjmmzBlVaxtRMIIeDbQKk0CH6Yx-0-547041357d77814504bcd9abdfc3adec)
当对不同类型的对象分别使用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=1739545986-thyN2eST6oK0QgfEwmDEFeLaFC5wskxX-0-93c2100b0e8fb62b74c5783f29ff5cb9)
运行结果如图6-3所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P150_17849.jpg?sign=1739545986-4IsTEgXb4xkkSpoTgHkA7l3Dhr5ZS2uv-0-10be41d155c9aa239fd7e04a9d76e6eb)
图6-3 运行结果
4.NSMutableString对象复制
NSMutableString对象的复制属于非容器类且可变对象的复制。通过下方的示例代码验证后,可以得到如下结论:NSMutableString类使用copy或者mutableCopy均为深复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T151_17994.jpg?sign=1739545986-rNGEO52KuhZcaHG6WkXeuiSg9U5FqigJ-0-63b908e17593e1a4c30139d6eec407c1)
运行结果如图6-4所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P151_17996.jpg?sign=1739545986-Iu1TH9jZA2Rv2LDpUahyzjSyfkxrS8Vj-0-c43ec9e4e4131d5a93034527e04bf224)
图6-4 运行结果
5.NSArray对象的复制
NSArray对象的复制属于容器类且不可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSArray类使用copy为浅复制,使用mutableCopy为深复制,另外,不论使用copy还是mutableCopy,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T151_18000.jpg?sign=1739545986-3GRMvs0fDdFchQCbQkgDC3b3qAbkTj2G-0-3ab205b72365bd6a460ef035ea3a5455)
运行结果如图6-5所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P152_18144.jpg?sign=1739545986-l9kJWSdSBBcLHsYDR46sPeYZPe2QFphB-0-56b785ca8e4ce5c3b5ddbbb0cca578a2)
图6-5 运行结果
6.NSMutableArray对象复制
NSMutableArray对象复制属于容器类且可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSMutableArray类不论使用copy还是mutableCopy都为深复制,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T152_18148.jpg?sign=1739545986-uTPKcKoXqVmmmAV6R0UapDAclUqpBvZm-0-62d30ce3acfb09ab6a1fcebbdbab2830)
运行结果如图6-6所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P153_18288.jpg?sign=1739545986-iIhKjcWADqWVG757oqRDjplL4GAtNNdE-0-c6d8e3d95f1653bc52236cc7972828ee)
图6-6 运行结果
7.NSDictionary对象复制
NSDictionary对象的复制属于容器类且不可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSDictionary类使用copy为浅复制,使用mutableCopy为深复制,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T153_82857.jpg?sign=1739545986-SKVFEw6BzJg6nX5d11eKBngTU2CORvV7-0-d855528188432f0977d69f2c7d7a4b6e)
运行结果如图6-7所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P154_18415.jpg?sign=1739545986-wjMs4InG9dlzBZ0Zk2TUfnweXqM04aTj-0-91f770cb0de4484a2a051f6119044e7d)
图6-7 运行结果
8.NSMutableDictionary对象复制
NSMutableDictionary对象复制属于容器类且可变对象的复制,通过下方的示例代码验证后,可以得到如下结论:NSMutableDictionary类不论使用copy还是mutableCopy均为深复制,容器内的对象都是浅复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T154_18419.jpg?sign=1739545986-ujwsb4NHB0OY5LYUgoTaxGFHyku302p0-0-e11f722c77c800675e1592d59005eb56)
运行结果如图6-8所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P154_18421.jpg?sign=1739545986-dz9pg5ExMvCYpwY5PmyhCX02V7Gjji5w-0-333209bfeb101d4b46ef12e9de772790)
图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=1739545986-ORlI8FcncHcdPqykD3GKGPDwl7XxOKiJ-0-ac2847df7d32d102b343c0d82e1e72bb)
为了实现对该类对象的复制,要求ClassB类遵守NSCopying协议,同时在类的.m文件中实现copyWithZone:方法,在该方法中的实现逻辑决定了当调用copy方法时,对该类对象进行复制所采取的方式(深复制或者浅复制)。
2.浅复制该类的对象
当仅仅需要对该对象进行浅复制时,可以在copyWithZone:方法中,直接返回要复制的对象即可。
- 在ClassB.m文件中,实现copyWithZone:方法。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T155_18529.jpg?sign=1739545986-jhqjOb1pBq0SFQpyEyAVa8y2SvkYmF5S-0-abdacbad111132f3021359d343d0b32e)
3.深复制该类的对象
当需要对自定义对象深复制时,需要在copyWithZone:方法中调用alloc以及init方法,重新开辟一块新的内存空间。另外,对于属性的复制过程中,也需要考虑到属性自身的特性,例如:有copy特性的属性需要重新生成新的副本,strong以及weak只需要做指针赋值即可。
- 在ClassB.m文件中,实现copyWithZone:方法。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T155_82858.jpg?sign=1739545986-8QR1P4Xqu07SVhu15pyB1mSgmeVImE4V-0-2648afc1f083b69ebd01a9d333752487)
4.深复制与浅复制代码验证
在main.m文件中,添加一个函数,用来复制ClassB的对象,代码如下所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T156_18643.jpg?sign=1739545986-0KaPykGW9TvjhyJhejghtcj3r241yuZ7-0-073ae46d9c4fc8c3943274fb6c7eeb66)
当ClassB中的copyWithZone:方法中采用浅复制时,运行结果如图6-9所示。复制后得到的副本指向同一内存地址,即进行了指针复制,内容还是原来的内容。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P156_18645.jpg?sign=1739545986-E10hu9U3fBbVl1Bq8Rz1XnGAzMk8HCVX-0-f94f800c0f2f3d7821bd2d847ff94879)
图6-9 运行结果
当ClassB中的copyWithZone:方法中采用深复制时,运行结果如图6-10所示。可以看到复制得到的对象与原对象的地址不同,同时属性中包含copy关键字的属性在复制过程中也进行了深复制,而strong/weak特性的属性仅仅做了指针复制。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P156_18649.jpg?sign=1739545986-SQnRNDdAg4cLvSZyPVQhOBwrc6qfWg5y-0-c0ef7b62ee442de17937a4d1b2b97813)
图6-10 运行结果