引子
淺拷貝:指針拷貝创千,引用拷貝缰雇,指向同一內(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)似乎沒什么問題,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ù)制索抓,解決引用的問題钧忽,當(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);
輸出:
似乎沒啥問題篮幢,確實(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í)沒有相互影響
原因解析
最初的時(shí)候
當(dāng)我們操作數(shù)組中的元素時(shí)(原始數(shù)組新增一個(gè)元素,mutableCopy的數(shù)組作刪除一個(gè)元素操作)缝裤,會(huì)發(fā)生如下變化
上圖可以看出,原始數(shù)組多出了一個(gè)“banana”的元素公荧,而可變數(shù)組被刪除了“pear”元素,指向“pear”元素的指針被移除了
過(guò)程雖然如此同规,但是并沒有影響到我們使用循狰,那我們是不是忽略其非深拷貝到事實(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报亩,如果僅僅做一次拷貝就是淺拷貝T『琛!弦追!
接下來(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)樵乜赡軟]有被拷貝盛龄,并且如果元素是不可變類型時(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í)灸拍,可以連元素一起拷貝做祝,屬于深拷貝(元素非容器)