一没陡、淺拷貝和深拷貝的定義
關(guān)于淺拷貝和深拷貝是如何定義的小腊,可能不同的人有不同的理解伏尼。據(jù)我了解(不知道有沒有偏差),蘋果對淺拷貝和深拷貝是這么定義的:淺拷貝可以理解為指針復(fù)制堪遂,新的指針并沒有指向一段新開辟的內(nèi)存介蛉,新指針?biāo)赶虻膬?nèi)存跟被拷貝指針?biāo)赶虻膬?nèi)存其實是同一塊內(nèi)存;深拷貝不是指針復(fù)制溶褪,新的指針指向了一段新開辟的內(nèi)存币旧,并且這個新開辟的內(nèi)存的內(nèi)容,跟被拷貝指針?biāo)赶虻膬?nèi)存的內(nèi)容是一樣的猿妈;如果拷貝的是非容器對象吹菱,例如NSString,NSNumber這些,相對來說比較好理解彭则。如果拷貝的是容器對象鳍刷,例如NSArray,NSDictionary,按照蘋果對淺拷貝和深拷貝的定義,對容器對象進(jìn)行所謂的深拷貝俯抖,其實也僅僅是容器的深拷貝输瓜,容器里面的元素,其實還是指針拷貝芬萍;如果要對容器對象實現(xiàn)完全意義上的深拷貝(容器和容器里的元素都是深拷貝尤揣,在內(nèi)存中完全獨(dú)立但內(nèi)容一樣的2份數(shù)據(jù)),則需要通過其他方式柬祠,具體有什么方式北戏,文章最后部分有介紹。
二瓶盛、對象拷貝的幾種情況
要透徹理解淺拷貝和深拷貝最欠,需要分以下幾種類型來討論:
1、不可修改的非容器類(如NSString)
2惩猫、可修改的非容器類(如NSMutableString)
3、不可修改的容器類(如NSArray蚜点、NSDictionary轧房、NSSet)
4、可修改的容器類(如NSMutableArray绍绘、NSMutableDictionary奶镶、NSMutableSet)
三迟赃、系統(tǒng)的非容器類(如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);
}
打印結(jié)果:
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的內(nèi)容后纤壁,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);
}
打印結(jié)果:
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調(diào)用copy酌媒,會開辟新的內(nèi)存,說明是深拷貝迄靠,但是返回的是一個不可修改的NSString(不信的話調(diào)用appendString立馬崩潰);
對NSMutableString調(diào)用mutableCopy,會開辟新的內(nèi)存秒咨,說明是深拷貝,返回的是一個可修改的NSMutableString;
四掌挚、不可修改容器NSArray的拷貝
其實怎么定義淺拷貝和深拷貝并非是最重要的雨席,重要的是我們要清楚理解在內(nèi)存層面,到底發(fā)生了什么改變吠式。下面準(zhǔn)備了幾個示例代碼陡厘,分別展示了對NSArray對象進(jìn)行拷貝的幾種可能情況,配合代碼打印結(jié)果和分析特占,方便讀者對比和理解糙置。首先準(zhǔn)備一個用來復(fù)制用的NSArray:
NSArray *array = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
然后演示ABCDEF共6個例子,對array對象進(jìn)行不同的"拷貝"操作:
示例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);
}
打印結(jié)果:
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] 執(zhí)行之后,deepCopyArray和array所指向的內(nèi)容地址是一樣的胖笛,即是同一個容器网持;
2、[array copy] 執(zhí)行之前, array的retainCount是1长踊,[array copy]執(zhí)行之后功舀,array的retainCount變成2,deepCopyArray的retainCount也是2,由此也可側(cè)面印證兩者指向的就是同一塊內(nèi)存(同一個NSArray容器)
3身弊、修改array的第一個元素的內(nèi)容后辟汰,deepCopyArray的第一個元素的內(nèi)容也跟著變了,證明這兩者指向的就是同一塊內(nèi)存(同一個NSArray容器)
結(jié)論:
上面對NSArray的copy是淺拷貝阱佛,容器和容器里的元素都是一樣的帖汞,唯一的變化就是引用計數(shù)器變成了2。那為什么NSArray的copy不新開辟一段內(nèi)存來做拷貝呢凑术?那是因為NSArray本身是一個不可修改的容器翩蘸,既然不可修改,就沒必要新開辟一段內(nèi)存淮逊,只需要引用計數(shù)器加一就夠了催首,大大節(jié)省了內(nèi)存開銷扶踊。
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);
}
打印結(jié)果:
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
)
分析:
(略)
結(jié)論:
array和deepCopyArray是不同的容器郎任,但元素卻是相同的秧耗,按蘋果的說法,也叫做深拷貝2爸巍(這里插入一張圖比較合適)
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);
}
打印結(jié)果:
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
)
分析:
(略)
結(jié)論:
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);
}
打印結(jié)果:
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
)
分析:(略)
結(jié)論:
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);
}
打印結(jié)果:
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
)
分析:(略)
結(jié)論:
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);
}
打印結(jié)果:
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
)
分析:
(略)
結(jié)論:
array和deepCopyArray是不同的容器弧械,但元素卻是相同的,按蘋果的說法空民,也叫做深拷貝H刑啤(跟示例B的效果是完全一樣的)
五、可修改的容器類NSMutableArray
上面詳細(xì)的列舉了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);
}
打印結(jié)果:
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
)
分析:(略)
結(jié)論:在這個例子里画饥,不僅修改了容器里的第一個元素的值,還往容器里新增加一個元素浊猾,根據(jù)打印結(jié)果可以知道抖甘,對NSMutableArray調(diào)用copy,是深拷貝葫慎,但僅僅是容器的深拷貝衔彻,容器里的元素是指針拷貝。
再來看另外一個例子偷办,對NSMutableArray進(jìn)行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);
}
打印結(jié)果:
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
)
分析:(略)
結(jié)論:在這個例子里艰额,不僅修改了容器里的第一個元素的值,還往容器里新增加一個元素椒涯,根據(jù)打印結(jié)果可以知道悴晰,對NSMutableArray調(diào)用copy,是深拷貝逐工,但僅僅是容器的深拷貝铡溪,容器里的元素是指針拷貝。(也就是跟上一個例子效果是一樣的)
六泪喊、對深淺拷貝的總結(jié)
1棕硫、對于不可修改的非容器類對象(至少我驗證過NSString),copy是淺拷貝袒啼,mutableCopy是深拷貝;
2哈扮、對于可修改的非容器類對象(至少我驗證過NSMutableString),copy是深拷貝(但它返回的是一個不可修改的對象)蚓再,mutableCopy是深拷貝;
3滑肉、對于不可修改的容器類對象(至少我驗證過NSArray), copy是淺拷貝,mutableCopy是深拷貝(但不是完全意義上的深拷貝摘仅,只是容器的深拷貝靶庙,元素仍然是淺拷貝);
4、對于可修改的容器類對象(至少我驗證過NSMutableArray),copy是深拷貝(但不是完全意義上的深拷貝娃属,只是容器的深拷貝六荒,元素仍然是淺拷貝), mutableCopy是深拷貝(但不是完全意義上的深拷貝,只是容器的深拷貝矾端,元素仍然是淺拷貝);
5掏击、那么問題來了,對于容器類對象秩铆,如何實現(xiàn)完全意義上的深拷貝呢(容器和元素的都是深拷貝)砚亭?
答案是用歸檔
NSArray* deepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
[NSKeyedArchiver archivedDataWithRootObject: array]];
(如果你不習(xí)慣什么淺拷貝和深拷貝,你可以將淺拷貝理解為沒有開辟新內(nèi)存殴玛,將深拷貝理解為開辟了新內(nèi)存)
七捅膘、自定義對象的copy和mutableCopy
ios中并不是所有的對象都支持copy,mutableCopy族阅,遵守NSCopying 協(xié)議的類可以發(fā)送copy消息篓跛,遵守NSMutableCopying 協(xié)議的類才可以發(fā)送mutableCopy消息。假如發(fā)送了一個沒有遵守上訴兩協(xié)議而發(fā)送 copy或者 mutableCopy,那么就會發(fā)生異常坦刀。但是默認(rèn)的ios類并沒有遵守這兩個協(xié)議愧沟。如果想自定義一下copy 那么就必須遵守NSCopying,并且實現(xiàn) copyWithZone: 方法,如果想自定義一下mutableCopy 那么就必須遵守NSMutableCopying,并且實現(xiàn) mutableCopyWithZone: 方法鲤遥。
參考資料
1沐寺、https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Collections/Articles/Copying.html
2、http://www.cnblogs.com/yswdarren/p/3611924.html