iOS 集合的深复制与浅复制
概念
对象拷贝有两种方式:浅复制和深复制。顾名思义,浅复制,并不拷贝对象本身,仅仅是拷贝指向对象的指针;深复制是直接拷贝整个对象内存到另一块内存中。
一图以蔽之
再简单些说:浅复制就是指针拷贝;深复制就是内容拷贝。
集合的浅复制 (shallow copy)
集合的浅复制有非常多种方法。当你进行浅复制时,会向原始的集合发送retain消息,引用计数加1,同时指针被拷贝到新的集合。
现在让我们看一些浅复制的例子:
NSArray *shallowCopyArray = [someArray copyWithZone:nil];
NSSet *shallowCopySet = [NSSet mutableCopyWithZone:nil];
NSDictionary *shallowCopyDict = [[NSDictionary alloc] initWithDictionary:someDictionary copyItems:NO];
集合的深复制 (deep copy)
集合的深复制有两种方法。可以用 initWithArray:copyItems: 将第二个参数设置为YES即可深复制,如
NSDictionary shallowCopyDict = [[NSDictionary alloc] initWithDictionary:someDictionary copyItems:YES];
如果你用这种方法深复制,集合里的每个对象都会收到 copyWithZone: 消息。如果集合里的对象遵循 NSCopying 协议,那么对象就会被深复制到新的集合。如果对象没有遵循 NSCopying 协议,而尝试用这种方法进行深复制,会在运行时出错。copyWithZone: 这种拷贝方式只能够提供一层内存拷贝(one-level-deep copy),而非真正的深复制。
第二个方法是将集合进行归档(archive),然后解档(unarchive),如:
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
集合的单层深复制 (one-level-deep copy)
看到这里,有同学会问:如果在多层数组中,对第一层进行内容拷贝,其它层进行指针拷贝,这种情况是属于深复制,还是浅复制?对此,苹果官网文档有这样一句话描述
This kind of copy is only capable of producing a one-level-deep copy. If you only need a one-level-deep copy...
If you need a true deep copy, such as when you have an array of arrays...
从文中可以看出,苹果认为这种复制不是真正的深复制,而是将其称为单层深复制(one-level-deep copy)。因此,网上有人对浅复制、深复制、单层深复制做了概念区分。
浅复制(shallow copy):在浅复制操作时,对于被复制对象的每一层都是指针复制。
深复制(one-level-deep copy):在深复制操作时,对于被复制对象,至少有一层是深复制。
完全复制(real-deep copy):在完全复制操作时,对于被复制对象的每一层都是对象复制。
当然,这些都是概念性的东西,没有必要纠结于此。只要知道进行拷贝操作时,被拷贝的是指针还是内容即可。
系统对象的copy与mutableCopy方法
不管是集合类对象,还是非集合类对象,接收到copy和mutableCopy消息时,都遵循以下准则:
copy返回imutable对象;所以,如果对copy返回值使用mutable对象接口就会crash;
mutableCopy返回mutable对象;
下面将针对非集合类对象和集合类对象的copy和mutableCopy方法进行具体的阐述
1、非集合类对象的copy与mutableCopy
系统非集合类对象指的是 NSString, NSNumber … 之类的对象。下面先看个非集合类immutable对象拷贝的例子
NSString *string = @"origin";
NSString *stringCopy = [string copy];
NSMutableString *stringMCopy = [string mutableCopy];
通过查看内存,可以看到 stringCopy 和 string 的地址是一样,进行了指针拷贝;而 stringMCopy 的地址和 string 不一样,进行了内容拷贝;
再看mutable对象拷贝例子
NSMutableString *string = [NSMutableString stringWithString: @"origin"];
//copy
NSString *stringCopy = [string copy];
NSMutableString *mStringCopy = [string copy];
NSMutableString *stringMCopy = [string mutableCopy];
//change value
[mStringCopy appendString:@"mm"]; //crash
[string appendString:@" origion!"];
[stringMCopy appendString:@"!!"];
运行以上代码,会在第7行crash,原因就是 copy 返回的对象是 immutable 对象。注释第7行后再运行,查看内存,发现 string、stringCopy、mStringCopy、stringMCopy 四个对象的内存地址都不一样,说明此时都是做内容拷贝。
综上两个例子,我们可以得出结论:
在非集合类对象中:对immutable对象进行copy操作,是指针复制,mutableCopy操作时内容复制;对mutable对象进行copy和mutableCopy都是内容复制。用代码简单表示如下:
[immutableObject copy] // 浅复制
[immutableObject mutableCopy] //深复制
[mutableObject copy] //深复制
[mutableObject mutableCopy] //深复制
2、集合类对象的copy与mutableCopy
集合类对象是指NSArray、NSDictionary、NSSet … 之类的对象。下面先看集合类immutable对象使用copy和mutableCopy的一个例子:
NSArray *array = @[@[@"a", @"b"], @[@"c", @"d"];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];
查看内容,可以看到copyArray和array的地址是一样的,而mCopyArray和array的地址是不同的。说明copy操作进行了指针拷贝,mutableCopy进行了内容拷贝。但需要强调的是:此处的内容拷贝,仅仅是拷贝array这个对象,array集合内部的元素仍然是指针拷贝。这和上面的非集合immutable对象的拷贝还是挺相似的,那么mutable对象的拷贝会不会类似呢?我们继续往下,看mutable对象拷贝的例子:
NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];
查看内存,如我们所料,copyArray、mCopyArray和array的内存地址都不一样,说明copyArray、mCopyArray都对array进行了内容拷贝。同样,我们可以得出结论:
在集合类对象中,对immutable对象进行copy,是指针复制,mutableCopy是内容复制;对mutable对象进行copy和mutableCopy都是内容复制。但是:集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制。用代码简单表示如下:
[immutableObject copy] // 浅复制
[immutableObject mutableCopy] //单层深复制
[mutableObject copy] //单层深复制
[mutableObject mutableCopy] //单层深复制
这个代码结论和非集合类的非常相似。
这时候,是不是有人要问了,如果要对集合对象复制元素怎么办?有这疑问的同学不妨回头看看集合的深复制。
好了,深复制与浅复制就讲到这里。
最后说个题外的东西,在搜集资料的过程中,发现一个有可能犯错的点
NSString *str = @"string";
str = @"newString";
上面这段代码,在执行第二行代码后,内存地址发生了变化。乍一看,有点意外。按照 C 语言的经验,初始化一个字符串之后,字符串的首地址就被确定下来,不管之后如何修改字符串内容,这个地址都不会改变。但此处第二行并不是对 str 指向的内存地址重新赋值,因为赋值操作符左边的 str 是一个指针,也就是说此处修改的是内存地址。
所以第二行应该这样理解:将@”newStirng”当做一个新的对象,将这段对象的内存地址赋值给str。
我有如下的两个方法查看内存地址
p str 会打印对象本身的内存地址和对象内容
(lldb) p str
(NSString *) $0 = 0x000000010c913680 @"a"
po &str 则打印的是引用对象的指针所在的地址
(lldb) po &str
0x00007fff532fb6c0
参考文档
[1] Apple Collections Programming Topics: Copying Collections
https://developer.apple.com/library/mac/documentation/cocoa/conceptual/Collections/Articles/Copying.html#//apple_ref/doc/uid/TP40010162-SW3
[2] iPhone Dev:iOS开发之深拷贝与浅拷贝(mutableCopy与Copy)详解 (博文可能有纰漏,童鞋仔细阅读亲自验证)
http://www.cnblogs.com/ydhliphonedev/archive/2012/04/27/2473927.html
[3] Blog:copy 与 mutableCopy
http://www.fanliugen.com/?p=278
iOS 集合的深复制与浅复制的更多相关文章
- iOS 之 深复制、浅复制
深复制不仅复制对象本身,对象持有的属性对象也做了复制. 浅复制之复制对象本身,不对里面的属性进行复制.
- 深复制与浅复制&&strong,copy修饰符总结
又是一个老生常谈的话题,可是貌似这个问题,好多ios开发工程师并不能理解透彻,所以简单记录分析一下深复制与浅复制的原理以及strong,copy修饰符的原理和使用. 一.深复制与浅复制 ...
- .Net深复制、浅复制
在.Net,大家都知道引用类型的深复制.浅复制吧. ,一般int等值类型是值类型(复制时是直接传值),一般的类(List<T>,Class)是引用类型(复制时传地址),默认是浅复制.若ob ...
- C++学习基础七——深复制与浅复制
一.深复制与浅复制基本知识 深复制和浅复制,又称为深拷贝和浅拷贝. 深复制和浅复制的区别如下图1所示: 图1 图1表示的是,定义一个类CDemo,包含int a和char *str两个成员变量, 当深 ...
- php对象当参数传递 && php深复制和浅复制
把对象当参数传递给方法,在方法里改过对象后,影响到外面的对象 因为对象是引用传递过去的 class Book { public $name; public function __construct( ...
- Java中对象的深复制和浅复制详解
1.浅复制与深复制概念 ⑴浅复制(浅克隆) 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象. ⑵ ...
- Java中的深复制与浅复制
1.浅复制与深复制概念 ⑴浅复制(浅克隆) 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅复制仅仅复制所考虑的对象,而不 复制它所引用的对象. ...
- C#深复制和浅复制
本文在于巩固基础 我们来看看什么是深复制.浅复制: C#中对于数据的复制机制虽然简单但是容易让人误解.C#数据类型大体分为值类型(value type)与引用类型(reference type).对于 ...
- C#深复制与浅复制
C#深复制与浅复制 C#中对于数据的复制机制虽然简单但是容易让人误解.C#数据类型大体分为值类型(value type)与引用类型(reference type).对于值类型数据,复制的时候直接将数据 ...
随机推荐
- WPF Window对象
户通过窗口与 Windows Presentation Foundation (WPF) 独立应用程序进行交互.窗口的主要用途是承载可视化数据并使用户可以与数据进行交互的内容.独立 WPF 应用程序使 ...
- 2的N次方
/**编程精确计算2的N次方.(N是介于100和1000之间的整数)*//*问题代码:#include<stdio.h>#include<math.h>int main(){ ...
- ruby特性
1. ruby类结构 每个类都是Class类的对象 所有类都继承自BasicObject类(Module类不能实例化) 2. 单例方法 单例方法可以不定义在类中,只为某个对象定义方法,所以称为单例方法 ...
- Laravel框架——任务调度(cron)
准备: 在服务的/var/spool/cron/root文件中添加代码 cd /var/spool/cron/root 添加以下代码 * * * * * phppath 项目路径/artisan sc ...
- 运行从别处复制过来的linux可执行程序
1, 首先ldd看看缺不缺so文件,如果不缺可忽略下面的文字,直接执行 2, 先看看缺的这些库在系统上有没有,这些库可通过安装开发包,第三方软件进行安装 3, 找到可能会包含这些库的可执行程序,ldd ...
- 根据http协议传送数据
发送的内容: [50 4f 53 54 20 2f 64 65 78 2f 66 69 72 65 70 6f 77 65 72 20 48 54 54 50 2f 31 2e 31 0d 0a 43 ...
- c# 测试
string input = "//a/@href "; int index = input.IndexOf("/@"); String attr = inpu ...
- tyvj P1135 - 植物大战僵尸 最大权闭合图
P1135 - 植物大战僵尸 From ytt Normal (OI)总时限:10s 内存限制:128MB 代码长度限制:64KB 背景 Background 虽然这么多天了,,虽然 ...
- UIWebView 自定义网页中的alert和confirm提示框风格
.h #import <UIKit/UIKit.h> @interface UIWebView (JavaScriptAlert) -(void)webView:(UIWebView *) ...
- c#回调函数写法
添加一个cs文件,在里面定义回调 using System; using System.Collections.Generic; using System.Linq; using System.Web ...