引子
淺拷貝:指針拷貝,引用拷貝,指向同一內(nèi)存地址
深拷貝:內(nèi)容拷貝,指向不同內(nèi)存地址,但是內(nèi)容相同
容器類拷貝的誤解
針對(duì)NSArray,copy是淺拷貝,mutableCopy是深拷貝
針對(duì)NSMutableArray,copy是深拷貝,mutableCopy是深拷貝
舉例說(shuō)明:
NSArray的拷貝
NSArray *itemsOrigin = @[@"apple",@"pear"];
NSArray *items_copy = itemsOrigin.copy;
NSArray *items_mutableCopy = itemsOrigin.mutableCopy;
NSLog(@"原始數(shù)組地址:%p",itemsOrigin);
NSLog(@"copy數(shù)組地址:%p",items_copy);
NSLog(@"mutableCopy數(shù)組地址:%p",items_mutableCopy);
輸出的內(nèi)存地址:
看來(lái)似乎沒(méi)什么問(wèn)題,copy后的數(shù)組指向同一內(nèi)存地址,mutableCopy產(chǎn)生新的內(nèi)存地址
NSMutableArray的拷貝
NSMutableArray *mutableItemsOrigin = [NSMutableArray arrayWithArray:@[@"apple",@"pear"]];
NSArray *mutableItems_copy = mutableItemsOrigin.copy;
NSMutableArray *mutableItems_mutableCopy = mutableItemsOrigin.mutableCopy;
NSLog(@"原始數(shù)組地址:%p",mutableItemsOrigin);
NSLog(@"copy數(shù)組地址:%p",mutableItems_copy);
NSLog(@"mutableCopy數(shù)組地址:%p",mutableItems_mutableCopy);
輸出的內(nèi)存地址:
這和誤解中說(shuō)的一致,針對(duì)NSMutableArray,copy是深拷貝,mutableCopy是深拷貝
但是我們不要忘了,深拷貝的意義在于,內(nèi)容完全的復(fù)制,解決引用的問(wèn)題,當(dāng)我們改變其中一個(gè)元素時(shí),相互不會(huì)影響
這里有人會(huì)反駁了,我改變了元素,的確不會(huì)影響到復(fù)制的數(shù)組,就像下面這樣:
// 改變?cè)紨?shù)組
[mutableItemsOrigin addObject:@"banana"];
// 改變mutableCopy數(shù)組
[mutableItems_mutableCopy addObject:@"grape"];
NSLog(@"原始數(shù)組元素:%@",mutableItemsOrigin);
NSLog(@"mutableCopy數(shù)組元素:%@",mutableItems_mutableCopy);
輸出:
似乎沒(méi)啥問(wèn)題,確實(shí)相互不會(huì)影響,說(shuō)到這里,有人可能會(huì)大罵LZSB!
那是不是筆者嘩眾取寵了呢?回答是NO,讓筆者一步步揭開容器類真正的深拷貝
首先,我們回到最初的數(shù)組,輸出它們的內(nèi)存地址
NSMutableArray *mutableItemsOrigin = [NSMutableArray arrayWithArray:@[@"apple",@"pear"]];
NSArray *mutableItems_copy = mutableItemsOrigin.copy;
NSMutableArray *mutableItems_mutableCopy = mutableItemsOrigin.mutableCopy;
NSLog(@"原始數(shù)組地址:%p",mutableItemsOrigin);
NSLog(@"copy數(shù)組地址:%p",mutableItems_copy);
NSLog(@"mutableCopy數(shù)組地址:%p",mutableItems_mutableCopy);
NSLog(@"\n");
for (NSString *itemString in mutableItemsOrigin) {
NSLog(@"原始數(shù)組元素地址:%p",itemString);
}
for (NSString *itemString in mutableItems_copy) {
NSLog(@"copy數(shù)組元素地址:%p",itemString);
}
for (NSString *itemString in mutableItems_mutableCopy) {
NSLog(@"mutableCopy數(shù)組元素地址:%p",itemString);
}
輸出元素內(nèi)存地址:
由上圖可知,容器數(shù)組無(wú)論是copy還是mutableCopy,元素指向了相同的內(nèi)存地址;由此可知,兩種拷貝方式僅僅產(chǎn)生了兩個(gè)內(nèi)存地址不同的容器,它們的使用的是同一份元素
那為什么內(nèi)容不相互影響呢?接著往下看
我們?yōu)樵紨?shù)組新增一個(gè)元素,mutableCopy的數(shù)組作刪除一個(gè)元素操作
// 改變?cè)紨?shù)組 -- 新增
[mutableItemsOrigin addObject:@"banana"];
// 改變mutableCopy數(shù)組 -- 刪除
[mutableItems_mutableCopy removeLastObject];
NSLog(@"原始數(shù)組元素:%@",mutableItemsOrigin);
NSLog(@"mutableCopy數(shù)組元素:%@",mutableItems_mutableCopy);
NSLog(@"\n");
for (NSString *itemString in mutableItemsOrigin) {
NSLog(@"原始數(shù)組元素地址:%p",itemString);
}
for (NSString *itemString in mutableItems_mutableCopy) {
NSLog(@"mutableCopy數(shù)組元素地址:%p",itemString);
}
輸出:
由上圖可知:
1、原始數(shù)組新增了一個(gè)內(nèi)存地址為:0x100002130,內(nèi)容為banana的元素,mutableCopy數(shù)組只剩內(nèi)存地址為0x100002090,內(nèi)容為apple的元素
2、原始數(shù)組和mutableCopy數(shù)組確實(shí)沒(méi)有相互影響
原因解析
最初的時(shí)候
當(dāng)我們操作數(shù)組中的元素時(shí)(原始數(shù)組新增一個(gè)元素,mutableCopy的數(shù)組作刪除一個(gè)元素操作),會(huì)發(fā)生如下變化
上圖可以看出,原始數(shù)組多出了一個(gè)“banana”的元素,而可變數(shù)組被刪除了“pear”元素,指向“pear”元素的指針被移除了
過(guò)程雖然如此,但是并沒(méi)有影響到我們使用,那我們是不是忽略其非深拷貝到事實(shí)了呢?錯(cuò),我們要知道,上述的例子中,元素都是不可變的,當(dāng)我們操作時(shí),都是操作了整個(gè)元素地址,要么被刪除(指針),要么新增元素(新地址),假如元素是可變的,我們只操作元素的子元素會(huì)發(fā)生什么呢?
重點(diǎn):操作可變數(shù)組可變?cè)?/strong>
我們將元素定義為可變的
// 定義一個(gè)可變?cè)財(cái)?shù)組
NSMutableArray *items = [NSMutableArray arrayWithArray:@[@"apple",@"pear"]];
// 新建原始數(shù)組
NSMutableArray *mutableItemsOrigin = [NSMutableArray arrayWithObject:items];
// copy該數(shù)組
NSArray *mutableItems_copy = mutableItemsOrigin.copy;
// mutableCopy該數(shù)組
NSMutableArray *mutableItems_mutableCopy = mutableItemsOrigin.mutableCopy;
NSLog(@"原始數(shù)組地址:%p",mutableItemsOrigin);
NSLog(@"copy數(shù)組地址:%p",mutableItems_copy);
NSLog(@"mutableCopy數(shù)組地址:%p",mutableItems_mutableCopy);
// 輸出元素的內(nèi)存地址
NSLog(@"\n");
for (NSMutableArray *items in mutableItemsOrigin) {
for (NSString *itemString in items) {
NSLog(@"原始數(shù)組元素地址:%p",itemString);
}
}
for (NSMutableArray *items in mutableItems_copy) {
for (NSString *itemString in items) {
NSLog(@"copy數(shù)組元素地址:%p",itemString);
}
}
for (NSMutableArray *items in mutableItems_mutableCopy) {
for (NSString *itemString in items) {
NSLog(@"mutableCopy數(shù)組元素地址:%p",itemString);
}
}
輸出內(nèi)存地址:
由上圖可知:同樣的,NSMutableArray數(shù)組的copy和mutableCopy都會(huì)產(chǎn)生內(nèi)存地址不同容器,但是元素依舊引用了同一份,屬于淺拷貝
接下來(lái),我們來(lái)改變其中可變?cè)?/p>
// 改變?cè)紨?shù)組 -- 新增
NSMutableArray *tmpItemsOrigin = mutableItemsOrigin.firstObject;
[tmpItemsOrigin addObject:@"banana"];
// 改變mutableCopy數(shù)組 -- 新增
NSMutableArray *tmpItems_mutableCopy = mutableItems_mutableCopy.firstObject;
[tmpItems_mutableCopy addObject:@"watermelon"];
NSLog(@"原始數(shù)組元素:%@",mutableItemsOrigin.firstObject);
NSLog(@"copy數(shù)組元素:%@",mutableItems_copy.firstObject);
NSLog(@"mutableCopy數(shù)組元素:%@",mutableItems_mutableCopy.firstObject);
for (NSMutableArray *items in mutableItemsOrigin) {
for (NSString *itemString in items) {
NSLog(@"原始數(shù)組元素地址:%p",itemString);
}
}
for (NSMutableArray *items in mutableItems_copy) {
for (NSString *itemString in items) {
NSLog(@"copy數(shù)組元素地址:%p",itemString);
}
}
for (NSMutableArray *items in mutableItems_mutableCopy) {
for (NSString *itemString in items) {
NSLog(@"mutableCopy數(shù)組元素地址:%p",itemString);
}
}
輸出結(jié)果:
我們看到,上述操作,無(wú)論是原始數(shù)組的新增,還是mutableCopy數(shù)組的新增都會(huì)相互影響,不僅如此,copy出來(lái)的不可變數(shù)組也被改變了??!
這種結(jié)果再次說(shuō)明,容器類的復(fù)制,無(wú)論是copy還是mutableCopy,如果僅僅做一次拷貝就是淺拷貝?。。?/em>
接下來(lái),我們來(lái)看下系統(tǒng)的一個(gè)方法,拷貝元素
- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag
這個(gè)方法是將items拷貝(copy)一份,不可變?cè)貫闇\拷貝,可變?cè)貏t為深拷貝容器
1、items為NSArray
MutableArray *mutableItemsOrigin = [NSMutableArray arrayWithObject:@[@"apple",@"pear"]];
NSArray *mutableItems_copy = [[NSArray alloc] initWithArray:mutableItemsOrigin copyItems:YES];
NSMutableArray *mutableItems_mutableCopy = [[NSMutableArray alloc] initWithArray:mutableItemsOrigin copyItems:YES];
NSLog(@"原始數(shù)組地址:%p",mutableItemsOrigin);
NSLog(@"copy數(shù)組地址:%p",mutableItems_copy);
NSLog(@"mutableCopy數(shù)組地址:%p",mutableItems_mutableCopy);
NSLog(@"\n");
NSLog(@"原始數(shù)組元素地址:%p",mutableItemsOrigin.firstObject);
NSLog(@"copy數(shù)組元素地址:%p",mutableItems_copy.firstObject);
NSLog(@"mutableCopy數(shù)組元素地址:%p",mutableItems_mutableCopy.firstObject);
結(jié)果:元素的內(nèi)存地址不變
2、items為NSMutableArray
MutableArray *items = [NSMutableArray arrayWithArray:@[@"apple",@"pear"]];
NSMutableArray *mutableItemsOrigin = [NSMutableArray arrayWithObject:items];
NSArray *mutableItems_copy = [[NSArray alloc] initWithArray:mutableItemsOrigin copyItems:YES];
NSMutableArray *mutableItems_mutableCopy = [[NSMutableArray alloc] initWithArray:mutableItemsOrigin copyItems:YES];
NSLog(@"原始數(shù)組地址:%p",mutableItemsOrigin);
NSLog(@"copy數(shù)組地址:%p",mutableItems_copy);
NSLog(@"mutableCopy數(shù)組地址:%p",mutableItems_mutableCopy);
NSLog(@"\n");
NSLog(@"原始數(shù)組元素地址:%p",mutableItemsOrigin.firstObject);
NSLog(@"copy數(shù)組元素地址:%p",mutableItems_copy.firstObject);
NSLog(@"mutableCopy數(shù)組元素地址:%p",mutableItems_mutableCopy.firstObject);
結(jié)果:元素變?yōu)椴豢勺償?shù)組,內(nèi)存地址改變
這樣,我們來(lái)重復(fù)之前的例子
MutableArray *items = [NSMutableArray arrayWithArray:@[@"apple",@"pear"]];
NSMutableArray *mutableItemsOrigin = [NSMutableArray arrayWithObject:items];
NSArray *mutableItems_copy = [[NSArray alloc] initWithArray:mutableItemsOrigin copyItems:YES];
NSMutableArray *mutableItems_mutableCopy = [[NSMutableArray alloc] initWithArray:mutableItemsOrigin copyItems:YES];
NSLog(@"原始數(shù)組地址:%p",mutableItemsOrigin);
NSLog(@"copy數(shù)組地址:%p",mutableItems_copy);
NSLog(@"mutableCopy數(shù)組地址:%p",mutableItems_mutableCopy);
NSLog(@"\n");
// 輸出元素地址
NSLog(@"原始數(shù)組元素地址:%p",mutableItemsOrigin.firstObject);
NSLog(@"copy數(shù)組元素地址:%p",mutableItems_copy.firstObject);
NSLog(@"mutableCopy數(shù)組元素地址:%p",mutableItems_mutableCopy.firstObject);
// 輸出最小元素地址
for (NSMutableArray *items in mutableItemsOrigin) {
for (NSString *itemString in items) {
NSLog(@"原始數(shù)組元素地址:%p",itemString);
}
}
for (NSMutableArray *items in mutableItems_copy) {
for (NSString *itemString in items) {
NSLog(@"copy數(shù)組元素地址:%p",itemString);
}
}
for (NSMutableArray *items in mutableItems_mutableCopy) {
for (NSString *itemString in items) {
NSLog(@"mutableCopy數(shù)組元素地址:%p",itemString);
}
}
結(jié)果:
我們發(fā)現(xiàn):
1、使用了copyItems函數(shù)后,產(chǎn)生了新的元素容器,但是最小元素的內(nèi)存地址依舊是一份
2、可變?cè)財(cái)?shù)組由于該函數(shù),變?yōu)榱瞬豢勺償?shù)組
3、最小單位的元素依舊指向同一份地址
這時(shí),由于元素?cái)?shù)組內(nèi)存地址已經(jīng)不同,我們操作原始數(shù)組時(shí),不會(huì)在影響到拷貝到數(shù)組
// 改變?cè)紨?shù)組 -- 新增
NSMutableArray *tmpItemsOrigin = mutableItemsOrigin.firstObject;
[tmpItemsOrigin addObject:@"banana"];
// 改變mutableCopy數(shù)組 -- 新增 -- 由于元素變?yōu)椴豢勺償?shù)組,所以不可像下面那樣操作
//NSMutableArray *tmpItems_mutableCopy = mutableItems_mutableCopy.firstObject;
//[tmpItems_mutableCopy addObject:@"watermelon"];
NSLog(@"原始數(shù)組元素:%@",mutableItemsOrigin.firstObject);
NSLog(@"copy數(shù)組元素:%@",mutableItems_copy.firstObject);
NSLog(@"mutableCopy數(shù)組元素:%@",mutableItems_mutableCopy.firstObject);
for (NSMutableArray *items in mutableItemsOrigin) {
for (NSString *itemString in items) {
NSLog(@"原始數(shù)組元素地址:%p",itemString);
}
}
for (NSMutableArray *items in mutableItems_copy) {
for (NSString *itemString in items) {
NSLog(@"copy數(shù)組元素地址:%p",itemString);
}
}
for (NSMutableArray *items in mutableItems_mutableCopy) {
for (NSString *itemString in items) {
NSLog(@"mutableCopy數(shù)組元素地址:%p",itemString);
}
}
結(jié)果:
copy的數(shù)組和mutableCopy數(shù)組并未被影響
系統(tǒng)提供的這種方法只不過(guò)就是元素?cái)?shù)組copy一下,并將數(shù)組轉(zhuǎn)換為不可變數(shù)組NSArray,這時(shí)操作了整個(gè)不可變?cè)財(cái)?shù)組,這樣就避免了相互影響(和操作NSArray一樣)
綜上所述
1、copy和mutableCopy只能作單層拷貝,針對(duì)容器類僅僅使用一次拷貝操作時(shí),只能產(chǎn)生新的容器,但是元素共用一份
2、如果想要拷貝元素,則需要調(diào)用系統(tǒng)方法copyItems,將元素copy一份(這個(gè)元素copy也僅僅是單層拷貝)
3、如果是容器嵌套容器時(shí),單單使用一次copy或者mutableCopy,嚴(yán)格意義上來(lái)說(shuō),是無(wú)法做到深拷貝的,因?yàn)樵乜赡軟](méi)有被拷貝,并且如果元素是不可變類型時(shí),無(wú)論怎么樣做都只能是引用拷貝,因?yàn)樽钚≡厥遣豢勺兊模瑑H僅有一份內(nèi)存地址
4、因此非容器類,如NSString,NSMutableString等,copy和mutableCopy作單層拷貝就達(dá)到了最終效果
驗(yàn)證
情況1:非容器類,不可變類型
NSString *string = @"LOLITA64";
NSString *string_copy = string.copy;
NSMutableString *string_mutableCopy = string.mutableCopy;
NSLog(@"string地址:%p",string);
NSLog(@"string_copy地址:%p",string_copy);
NSLog(@"string_mutableCopy地址:%p",string_mutableCopy);
[string_mutableCopy appendString:@"-test"];
NSLog(@"原始字符串:%@",string);
NSLog(@"string_copy字符串:%@",string_copy);
NSLog(@"string_mutableCopy字符串:%@",string_mutableCopy);
輸出:
結(jié)論:copy為淺拷貝,mutableCopy為深拷貝
情況2:非容器類,可變類型
NSString *string = [NSMutableString stringWithString:@"LOLITA64"];
NSString *string_copy = string.copy;
NSMutableString *string_mutableCopy = string.mutableCopy;
NSLog(@"string地址:%p",string);
NSLog(@"string_copy地址:%p",string_copy);
NSLog(@"string_mutableCopy地址:%p",string_mutableCopy);
[string_mutableCopy appendString:@"-test"];
NSLog(@"原始字符串:%@",string);
NSLog(@"string_copy字符串:%@",string_copy);
NSLog(@"string_mutableCopy字符串:%@",string_mutableCopy);
輸出:
結(jié)論:copy為深拷貝,mutableCopy為深拷貝
情況3:容器類(單層),不可變類型,元素不可變非容器,單次拷貝
NSArray *items = @[@"apple",@"pear"];
NSArray *items_copy = items.copy;
NSMutableArray *items_mutableCopy = items.mutableCopy;
NSLog(@"items地址:%p",items);
NSLog(@"items_copy地址:%p",items_copy);
NSLog(@"items_mutableCopy地址:%p",items_mutableCopy);
for (NSString *itemString in items) {
NSLog(@"items不可變?cè)氐刂罚?p",itemString);
}
for (NSString *itemString in items_copy) {
NSLog(@"items_copy不可變?cè)氐刂罚?p",itemString);
}
for (NSString *itemString in items_mutableCopy) {
NSLog(@"items_mutableCopy不可變?cè)氐刂罚?p",itemString);
}
輸出:
結(jié)論:copy不產(chǎn)生新容器,mutableCopy產(chǎn)生新容器,但是最小元素內(nèi)存地址都一樣,淺拷貝
情況4:容器類(單層),不可變類型,元素不可變非容器,元素拷貝
// 更改
NSArray *items_copy = [[NSArray alloc] initWithArray:items copyItems:YES];
NSMutableArray *items_mutableCopy = [[NSMutableArray alloc] initWithArray:items copyItems:YES];
輸出:
結(jié)論:產(chǎn)生新容器,但是最小元素內(nèi)存地址都一樣,淺拷貝
情況5:容器類(單層),不可變類型,元素可變非容器,單次拷貝
NSMutableString *string1 = [NSMutableString stringWithString:@"apple"];
NSMutableString *string2 = [NSMutableString stringWithString:@"pear"];
NSArray *items = @[string1,string2];
NSArray *items_copy = items.copy;
NSMutableArray *items_mutableCopy = items.mutableCopy;
結(jié)果:
結(jié)論:copy不產(chǎn)生新容器,mutableCopy產(chǎn)生新容器,但是最小元素內(nèi)存地址都一樣,淺拷貝
情況6:容器類(單層),不可變類型,元素可變非容器,元素拷貝
NSArray *items_copy = [[NSArray alloc] initWithArray:items copyItems:YES];
NSMutableArray *items_mutableCopy = [[NSMutableArray alloc] initWithArray:items copyItems:YES];
輸出:
結(jié)論:產(chǎn)生新容器,最小元素內(nèi)存地址不一樣,深拷貝
情況7:容器類(單層),可變類型,元素不可變非容器,單次拷貝
NSMutableArray *items = [NSMutableArray arrayWithArray:@[@"apple",@"pear"]];
NSArray *items_copy = items.copy;
NSMutableArray *items_mutableCopy = items.mutableCopy;
輸出:
結(jié)論:copy、mutableCopy都產(chǎn)生新容器,但是最小元素內(nèi)存地址都一樣,淺拷貝
情況8:容器類(單層),可變類型,元素不可變,元素拷貝
NSMutableArray *items = [NSMutableArray arrayWithArray:@[@"apple",@"pear"]];
NSArray *items_copy = [[NSArray alloc] initWithArray:items copyItems:YES];
NSMutableArray *items_mutableCopy = [[NSMutableArray alloc] initWithArray:items copyItems:YES];
輸出:
結(jié)論:copy、mutableCopy都產(chǎn)生新容器,但是最小元素內(nèi)存地址都一樣,淺拷貝
情況9:容器類(單層),可變類型,元素可變,單次拷貝
NSMutableString *string1 = [NSMutableString stringWithString:@"apple"];
NSMutableString *string2 = [NSMutableString stringWithString:@"pear"];
NSMutableArray *items = [NSMutableArray arrayWithArray:@[string1,string2]];
NSArray *items_copy = items.copy;
NSMutableArray *items_mutableCopy = items.mutableCopy;
輸出:
結(jié)論:copy、mutableCopy都產(chǎn)生新容器,但是最小元素內(nèi)存地址都一樣,淺拷貝
情況10:容器類(單層),可變類型,元素可變,元素拷貝
NSArray *items_copy = [[NSArray alloc] initWithArray:items copyItems:YES];
NSMutableArray *items_mutableCopy = [[NSMutableArray alloc] initWithArray:items copyItems:YES];
輸出:
結(jié)論:產(chǎn)生新容器,最小元素內(nèi)存地址不一樣,深拷貝
情況11:容器類(多層,嵌套容器)
參考開始幾個(gè)例子
結(jié)論:
1、容器類是否可以深拷貝,首先要看最小元素,若最小元素為不可變類型,容器類的任何拷貝操作都是淺拷貝
2、如使用copyItems函數(shù),并且元素為可變類型時(shí),可以連元素一起拷貝,屬于深拷貝(元素非容器)