浅拷贝和深拷贝(copy和mutableCopy)的详解

一、浅拷贝和深拷贝的定义
关于浅拷贝和深拷贝是如何定义的,可能不同的人有不同的理解。据我了解(不知道有没有偏差),苹果对浅拷贝和深拷贝是这么定义的:浅拷贝可以理解为指针复制,新的指针并没有指向一段新开辟的内存,新指针所指向的内存跟被拷贝指针所指向的内存其实是同一块内存;深拷贝不是指针复制,新的指针指向了一段新开辟的内存,并且这个新开辟的内存的内容,跟被拷贝指针所指向的内存的内容是一样的;如果拷贝的是非容器对象,例如NSString,NSNumber这些,相对来说比较好理解。如果拷贝的是容器对象,例如NSArray,NSDictionary,按照苹果对浅拷贝和深拷贝的定义,对容器对象进行所谓的深拷贝,其实也仅仅是容器的深拷贝,容器里面的元素,其实还是指针拷贝;如果要对容器对象实现完全意义上的深拷贝(容器和容器里的元素都是深拷贝,在内存中完全独立但内容一样的2份数据),则需要通过其他方式,具体有什么方式,文章最后部分有介绍。

二、对象拷贝的几种情况
要透彻理解浅拷贝和深拷贝,需要分以下几种类型来讨论:
1、不可修改的非容器类(如NSString)
2、可修改的非容器类(如NSMutableString)
3、不可修改的容器类(如NSArray、NSDictionary、NSSet)
4、可修改的容器类(如NSMutableArray、NSMutableDictionary、NSMutableSet)

三、系统的非容器类(如NSString,NSMutableString等)
首先看一个例子

- (void)testStringCopyA {
    NSString *string = @"origion";
    NSString *stringCopy = [string copy];
    NSMutableString *stringMCopy = [string mutableCopy];
    NSLog(@"string address: %p stringCopy address:%p stringMCopy address:%p", string, stringCopy, stringMCopy);
    NSLog(@"string retainCount:%ld stringCopy retainCount:%ld stringMCopy retainCount:%ld", [string retainCount], [stringCopy retainCount], [stringMCopy retainCount]);
    [stringMCopy appendString:@"!!"];
    NSLog(@"string: %@ stringCopy: %@ stringMCopy:%@", string, stringCopy, stringMCopy);
}

打印结果:
string address: 0x1153554b0 stringCopy address:0x1153554b0 stringMCopy address:0x6000006692c0
string retainCount:-1 stringCopy retainCount:-1 stringMCopy retainCount:1
string: origion stringCopy: origion stringMCopy:origion!!

分析:string和stringCopy指向的地址是相同的,而stringMCopy指向的地址则不同,修改stringMCopy的内容后,string和stringCopy并没有变化,说明stringMCopy是深拷贝,stringCopy是浅拷贝

再看多一个例子:

- (void)testStringCopyB {
    NSMutableString *string = [[NSMutableString alloc] initWithString:@"origion"];
    NSString *stringCopy = [string copy]; // 注意这里copy返回的是一个不可修改的NSString,而非NSMutableString
    NSMutableString *stringMCopy = [string mutableCopy];
    NSLog(@"string address: %p stringCopy address:%p stringMCopy address:%p", string, stringCopy, stringMCopy);
    NSLog(@"string retainCount:%ld stringCopy retainCount:%ld stringMCopy retainCount:%ld", [string retainCount], [stringCopy retainCount], [stringMCopy retainCount]);
    [stringMCopy appendString:@"!!"];
    NSLog(@"string: %@ stringCopy: %@ stringMCopy:%@", string, stringCopy, stringMCopy);
}

打印结果:
string address: 0x608000478280 stringCopy address:0xa6e6f696769726f7 stringMCopy address:0x608000478240
string retainCount:1 stringCopy retainCount:-1 stringMCopy retainCount:1
string: origion stringCopy: origion stringMCopy:origion!!
分析:
对NSMutableString调用copy,会开辟新的内存,说明是深拷贝,但是返回的是一个不可修改的NSString(不信的话调用appendString立马崩溃);
对NSMutableString调用mutableCopy,会开辟新的内存,说明是深拷贝,返回的是一个可修改的NSMutableString;

四、不可修改容器NSArray的拷贝
其实怎么定义浅拷贝和深拷贝并非是最重要的,重要的是我们要清楚理解在内存层面,到底发生了什么改变。下面准备了几个示例代码,分别展示了对NSArray对象进行拷贝的几种可能情况,配合代码打印结果和分析,方便读者对比和理解。首先准备一个用来复制用的NSArray:

NSArray *array = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];

然后演示ABCDEF共6个例子,对array对象进行不同的"拷贝"操作:
示例A:

NSArray *deepCopyArray=[array copy];

示例B:

NSArray *deepCopyArray=[[NSArray alloc] initWithArray:array];

示例C:

NSArray *deepCopyArray=[[NSArray alloc] initWithArray: array copyItems: NO];

示例D:

NSArray *deepCopyArray=[[NSArray alloc] initWithArray: array copyItems: YES];

示例E:

NSArray *deepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
                              [NSKeyedArchiver archivedDataWithRootObject: array]];

示例F:

NSArray *deepCopyArray=[array mutableCopy];

1、示例A

- (void)testArrayDeepCopyA {
    NSArray *array = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
    NSArray *deepCopyArray=[array copy];
    
    NSLog(@"array address:%p deepCopyArray address:%p", array, deepCopyArray);
    NSLog(@"array retainCount: %ld deepCopyArray retainCount: %ld",[array retainCount], [deepCopyArray retainCount]);
    
    NSLog(@"\narray : %@ \ndeepCopyArray : %@", array, deepCopyArray);
    [[array objectAtIndex:0] appendString:@",you are changed"];
    NSLog(@"\narray : %@ \ndeepCopyArray : %@", array, deepCopyArray);
}

打印结果:
array address:0x608000253830 deepCopyArray address:0x608000253830
array retainCount: 2 deepCopyArray retainCount: 2
array : (
a,
b,
c
)
deepCopyArray : (
a,
b,
c
)
array : (
"a,you are changed",
b,
c
)
deepCopyArray : (
"a,you are changed",
b,
c
)

分析:
1、[array copy] 执行之后,deepCopyArray和array所指向的内容地址是一样的,即是同一个容器;
2、[array copy] 执行之前, array的retainCount是1,[array copy]执行之后,array的retainCount变成2,deepCopyArray的retainCount也是2,由此也可侧面印证两者指向的就是同一块内存(同一个NSArray容器)
3、修改array的第一个元素的内容后,deepCopyArray的第一个元素的内容也跟着变了,证明这两者指向的就是同一块内存(同一个NSArray容器)

结论:
上面对NSArray的copy是浅拷贝,容器和容器里的元素都是一样的,唯一的变化就是引用计数器变成了2。那为什么NSArray的copy不新开辟一段内存来做拷贝呢?那是因为NSArray本身是一个不可修改的容器,既然不可修改,就没必要新开辟一段内存,只需要引用计数器加一就够了,大大节省了内存开销。

2、示例B

- (void)testArrayDeepCopyB {
    NSArray *array = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
    NSArray *deepCopyArray=[[NSArray alloc] initWithArray:array];
    
    NSLog(@"array address:%p deepCopyArray address:%p", array, deepCopyArray);
    NSLog(@"array retainCount: %ld deepCopyArray retainCount: %ld",[array retainCount], [deepCopyArray retainCount]);
    
    NSLog(@"\narray : %@ \ndeepCopyArray : %@", array, deepCopyArray);
    [[array objectAtIndex:0] appendString:@",you are changed"];
    NSLog(@"\narray : %@ \ndeepCopyArray : %@", array, deepCopyArray);
}

打印结果:
array address:0x600000443b40 deepCopyArray address:0x6000004427c0
array retainCount: 1 deepCopyArray retainCount: 1
array : (
a,
b,
c
)
deepCopyArray : (
a,
b,
c
)
array : (
"a,you are changed",
b,
c
)
deepCopyArray : (
"a,you are changed",
b,
c
)

分析:
(略)

结论:
array和deepCopyArray是不同的容器,但元素却是相同的,按苹果的说法,也叫做深拷贝!(这里插入一张图比较合适)

3、示例C

- (void)testArrayDeepCopyC {
    NSArray *array = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
    NSArray *deepCopyArray=[[NSArray alloc] initWithArray: array copyItems: NO];
    
    NSLog(@"array address:%p deepCopyArray address:%p", array, deepCopyArray);
    NSLog(@"array retainCount: %ld deepCopyArray retainCount: %ld",[array retainCount], [deepCopyArray retainCount]);
    
    NSLog(@"\narray : %@ \ndeepCopyArray : %@", array, deepCopyArray);
    [[array objectAtIndex:0] appendString:@",you are changed"];
    NSLog(@"\narray : %@ \ndeepCopyArray : %@", array, deepCopyArray);
}

打印结果:
array address:0x600000442910 deepCopyArray address:0x600000441380
array retainCount: 1 deepCopyArray retainCount: 1
array : (
a,
b,
c
)
deepCopyArray : (
a,
b,
c
)
array : (
"a,you are changed",
b,
c
)
deepCopyArray : (
"a,you are changed",
b,
c
)

分析:
(略)

结论:
array和deepCopyArray是不同的容器,但元素却是相同的,按苹果的说法,也叫做深拷贝!(跟示例B的效果是完全一样的)

4、示例D

- (void)testArrayDeepCopyD {
    NSArray *array = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
    NSArray *deepCopyArray=[[NSArray alloc] initWithArray: array copyItems: YES];
    
    NSLog(@"array address:%p deepCopyArray address:%p", array, deepCopyArray);
    NSLog(@"array retainCount: %ld deepCopyArray retainCount: %ld",[array retainCount], [deepCopyArray retainCount]);
    
    NSLog(@"\narray : %@ \ndeepCopyArray : %@", array, deepCopyArray);
    [[array objectAtIndex:0] appendString:@",you are changed"];
    NSLog(@"\narray : %@ \ndeepCopyArray : %@", array, deepCopyArray);
}

打印结果:
array address:0x6080004435d0 deepCopyArray address:0x608000443150
array retainCount: 1 deepCopyArray retainCount: 1
array : (
a,
b,
c
)
deepCopyArray : (
a,
b,
c
)
array : (
"a,you are changed",
b,
c
)
deepCopyArray : (
a,
b,
c
)

分析:(略)

结论:
array和deepCopyArray是不同的容器,里面的元素也是不相同的,这就是完全意义上的深拷贝了

5、示例E

- (void)testArrayDeepCopyE {
    NSArray *array = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
    NSArray* deepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
                              [NSKeyedArchiver archivedDataWithRootObject: array]];
    
    NSLog(@"array address:%p deepCopyArray address:%p", array, deepCopyArray);
    NSLog(@"array retainCount: %ld deepCopyArray retainCount: %ld",[array retainCount], [deepCopyArray retainCount]);
    
    NSLog(@"\narray : %@ \ndeepCopyArray : %@", array, deepCopyArray);
    [[array objectAtIndex:0] appendString:@",you are changed"];
    NSLog(@"\narray : %@ \ndeepCopyArray : %@", array, deepCopyArray);
}

打印结果:
array address:0x60000005f980 deepCopyArray address:0x600000245670
array retainCount: 1 deepCopyArray retainCount: 1
array : (
a,
b,
c
)
deepCopyArray : (
a,
b,
c
)
array : (
"a,you are changed",
b,
c
)
deepCopyArray : (
a,
b,
c
)

分析:(略)

结论:
array和deepCopyArray是不同的容器,里面的元素也是不相同的,这就是完全意义上的深拷贝了(也就是效果跟示例D一样)

6、示例F

- (void)testArrayDeepCopyF {
    NSArray *array = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
    NSArray *deepCopyArray=[array mutableCopy];
    
    NSLog(@"array address:%p deepCopyArray address:%p", array, deepCopyArray);
    NSLog(@"array retainCount: %ld deepCopyArray retainCount: %ld",[array retainCount], [deepCopyArray retainCount]);
    
    NSLog(@"\narray : %@ \ndeepCopyArray : %@", array, deepCopyArray);
    [[array objectAtIndex:0] appendString:@",you are changed"];
    NSLog(@"\narray : %@ \ndeepCopyArray : %@", array, deepCopyArray);
}

打印结果:
array address:0x6000004432d0 deepCopyArray address:0x600000250d70
array retainCount: 1 deepCopyArray retainCount: 1
array : (
a,
b,
c
)
deepCopyArray : (
a,
b,
c
)
array : (
"a,you are changed",
b,
c
)
deepCopyArray : (
"a,you are changed",
b,
c
)

分析:
(略)

结论:
array和deepCopyArray是不同的容器,但元素却是相同的,按苹果的说法,也叫做深拷贝!(跟示例B的效果是完全一样的)

五、可修改的容器类NSMutableArray
上面详细的列举了NSArray拷贝的情况,下面简单介绍一下NSMutableArray,先看一个例子

- (void)testArrayDeepCopyG {
    NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
    NSMutableArray *deepCopyArray=[array copy]; // 这里返回的是可修改的容器NSMutableArray
    
    NSLog(@"array address:%p deepCopyArray address:%p", array, deepCopyArray);
    NSLog(@"array retainCount: %ld deepCopyArray retainCount: %ld",[array retainCount], [deepCopyArray retainCount]);
    
    NSLog(@"\narray : %@ \ndeepCopyArray : %@", array, deepCopyArray);
    [[array objectAtIndex:0] appendString:@",you are changed"];
    [array addObject:@"d"];
    NSLog(@"\narray : %@ \ndeepCopyArray : %@", array, deepCopyArray);
}

打印结果:
array address:0x6000002433c0 deepCopyArray address:0x600000457be0
array retainCount: 1 deepCopyArray retainCount: 1
array : (
a,
b,
c
)
deepCopyArray : (
a,
b,
c
)
array : (
"a,you are changed",
b,
c,
d
)
deepCopyArray : (
"a,you are changed",
b,
c
)
分析:(略)

结论:在这个例子里,不仅修改了容器里的第一个元素的值,还往容器里新增加一个元素,根据打印结果可以知道,对NSMutableArray调用copy,是深拷贝,但仅仅是容器的深拷贝,容器里的元素是指针拷贝。

再来看另外一个例子,对NSMutableArray进行mutableCopy

- (void)testArrayDeepCopyH {
    NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
    NSMutableArray *deepCopyArray=[array mutableCopy];
    
    NSLog(@"array address:%p deepCopyArray address:%p", array, deepCopyArray);
    NSLog(@"array retainCount: %ld deepCopyArray retainCount: %ld",[array retainCount], [deepCopyArray retainCount]);
    
    NSLog(@"\narray : %@ \ndeepCopyArray : %@", array, deepCopyArray);
    [[array objectAtIndex:0] appendString:@",you are changed"];
    [array addObject:@"d"];
    NSLog(@"\narray : %@ \ndeepCopyArray : %@", array, deepCopyArray);
}

打印结果:
array address:0x60000044afe0 deepCopyArray address:0x60000024c3f0
array retainCount: 1 deepCopyArray retainCount: 1
array : (
a,
b,
c
)
deepCopyArray : (
a,
b,
c
)
array : (
"a,you are changed",
b,
c,
d
)
deepCopyArray : (
"a,you are changed",
b,
c
)

分析:(略)

结论:在这个例子里,不仅修改了容器里的第一个元素的值,还往容器里新增加一个元素,根据打印结果可以知道,对NSMutableArray调用copy,是深拷贝,但仅仅是容器的深拷贝,容器里的元素是指针拷贝。(也就是跟上一个例子效果是一样的)

六、对深浅拷贝的总结
1、对于不可修改的非容器类对象(至少我验证过NSString),copy是浅拷贝,mutableCopy是深拷贝;
2、对于可修改的非容器类对象(至少我验证过NSMutableString),copy是深拷贝(但它返回的是一个不可修改的对象),mutableCopy是深拷贝;
3、对于不可修改的容器类对象(至少我验证过NSArray), copy是浅拷贝,mutableCopy是深拷贝(但不是完全意义上的深拷贝,只是容器的深拷贝,元素仍然是浅拷贝);
4、对于可修改的容器类对象(至少我验证过NSMutableArray),copy是深拷贝(但不是完全意义上的深拷贝,只是容器的深拷贝,元素仍然是浅拷贝), mutableCopy是深拷贝(但不是完全意义上的深拷贝,只是容器的深拷贝,元素仍然是浅拷贝);
5、那么问题来了,对于容器类对象,如何实现完全意义上的深拷贝呢(容器和元素的都是深拷贝)?
答案是用归档

    NSArray* deepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
                              [NSKeyedArchiver archivedDataWithRootObject: array]];
(如果你不习惯什么浅拷贝和深拷贝,你可以将浅拷贝理解为没有开辟新内存,将深拷贝理解为开辟了新内存)

七、自定义对象的copy和mutableCopy
ios中并不是所有的对象都支持copy,mutableCopy,遵守NSCopying 协议的类可以发送copy消息,遵守NSMutableCopying 协议的类才可以发送mutableCopy消息。假如发送了一个没有遵守上诉两协议而发送 copy或者 mutableCopy,那么就会发生异常。但是默认的ios类并没有遵守这两个协议。如果想自定义一下copy 那么就必须遵守NSCopying,并且实现 copyWithZone: 方法,如果想自定义一下mutableCopy 那么就必须遵守NSMutableCopying,并且实现 mutableCopyWithZone: 方法。

参考资料
1、https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Collections/Articles/Copying.html
2、http://www.cnblogs.com/yswdarren/p/3611924.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容