先說下概念,我們對變量的復(fù)制淮腾,其實(shí)就是在寫代碼的過程中糟需,再定義多幾個(gè)不同名字的變量,讓他們都“等于”某一個(gè)變量谷朝,這個(gè)過程我認(rèn)為就是我們平常說的“復(fù)制”洲押。
基本數(shù)據(jù)類型
對于基本數(shù)據(jù)類型,如int圆凰,double杈帐,BOOL這些,在賦值的過程中就是真正意義上的復(fù)制了,賦值時(shí)不僅把值傳遞到新的變量中挑童,而且新的變量也 重新開辟了內(nèi)存 累铅,使得原來的變量和后來的變量所指的不是同一塊內(nèi)存,就如同現(xiàn)實(shí)中真的復(fù)制(克抡镜稹)了一個(gè)新的一模一樣個(gè)體一樣娃兽。于是,我們把這種有 新開辟內(nèi)存 的復(fù)制大年,暫且先叫做 深復(fù)制(深復(fù)制也有分兩種换薄,后面會說到)。
- 實(shí)驗(yàn)代碼:
int a = 0;
int b = a;
JMPLog(&a);
JMPLog(&b);
- 運(yùn)行結(jié)果:
0x7ffee662c1dc
0x7ffee662c1d8
結(jié)論1:所有基本數(shù)據(jù)類型的復(fù)制翔试,都是深復(fù)制
非集合類型對象
所謂非集合類型對象轻要,比較常用的就是NSString,下面就以NSString作為例子垦缅,說明copy和mutableCopy之間的區(qū)別冲泥,并與深復(fù)制淺復(fù)制進(jìn)行聯(lián)系。
首先展示一個(gè)錯(cuò)誤的示范壁涎,網(wǎng)上有很多關(guān)于copy和mutableCopy的文章凡恍,里面舉了這么一個(gè)例子:
NSString *str1 = @"str1";
NSString *str2 = [str1 copy];
str1 = @"asdf";
NSLog(@"\nstr1 = %@ str1P = %p \n str2 = %@ str2P = %p", str1, str1, str2, str2);
/*輸出結(jié)果,修改str2 同理
str1 = asdf str1P = 0x10776b1a0
str2 = str1 str2P = 0x10776b180
*/
然后就說,因?yàn)閟tr2 = str1的時(shí)候怔球,兩個(gè)字符串都是不可變的嚼酝,指向的同一塊內(nèi)存空間中的 @"str1",是不可能變成@"abcd"的。所以這個(gè)時(shí)候竟坛,為了優(yōu)化性能闽巩,系統(tǒng)沒必要另外提供內(nèi)存,只生成另外一個(gè)指針担汤,指向同一塊內(nèi)存空間就行涎跨。
但是當(dāng)你從新給 str1 或者str2賦值的時(shí)候,因?yàn)橹暗膬?nèi)容不可變崭歧,還有互不影響的原則下隅很,這個(gè)時(shí)候,系統(tǒng)會從新開辟一塊內(nèi)存空間率碾。
上面的解釋和代碼有個(gè)很嚴(yán)重的問題叔营,當(dāng)執(zhí)行str1 = @"asdf";
這行代碼的時(shí)候其實(shí)str1的指針已經(jīng)指向了新的字符串@“asdf”身上了,所以這并不能很好的說明深淺復(fù)制的問題播掷。
言歸正傳审编,我們先討論對于不可變的非集合類型對象(這里用NSString作為例子),當(dāng)發(fā)送copy和mutableCopy消息后歧匈,新的對象的內(nèi)存情況。
- 實(shí)驗(yàn)代碼:
NSString *strA = @"strA";
NSString *strB = [strA copy];
NSString *strC = [strA mutableCopy];
NSLog(@"Value -- strA: %@, strB: %@, strC: %@", strA, strB, strC);
NSLog(@"Pointer -- strA: %p, strB: %p, strC: %p", &strA, &strB, &strC);
NSLog(@"Pointer of value -- strA: %p, strB: %p, strC: %p", strA, strB, strC);
- 運(yùn)行結(jié)果:
Value -- strA: strA, strB: strA, strC: strA
Pointer -- strA: 0x7ffee3adf1d8, strB: 0x7ffee3adf1d0, strC: 0x7ffee3adf1c8
Pointer of value -- strA: 0x10c124800, strB: 0x10c124800, strC: 0x6000009e0a80
結(jié)果分析:
- 可以看出砰嘁,三個(gè)字符串的內(nèi)容都是一樣的件炉,達(dá)到了我們對“復(fù)制”這個(gè)概念的目的勘究。但是,字符串B和C是不是做到真正意義上的復(fù)制呢斟冕?我們要看B口糕,C變量所指向的內(nèi)存地址(Pointer of value),結(jié)果顯示B的地址與A的地址一致(0x1039f0800)磕蛇,而C的地址則與A的不同(A: 0x1039f0800, C: 0x6000021f4450)景描,所以只有C才是真正意義上的復(fù)制,也就是我們上面提到的 深復(fù)制 秀撇,而像字符串B這種 只是把指針指向同一塊內(nèi)存地址超棺,而實(shí)現(xiàn)對應(yīng)內(nèi)容“復(fù)制” 的做法,我們稱之為 淺復(fù)制呵燕。
- 觀察三個(gè)字符串變量本身的地址(注意不是字符串所指向的地址)棠绘,發(fā)現(xiàn)三個(gè)是不同的,也就是說上述行為是新建了三個(gè)指針(指針本身也占有內(nèi)存)再扭,然后A氧苍,B指向的是同一塊地址,C則指向另一塊新的地址泛范,這些內(nèi)存地址的內(nèi)容都是@“strA”让虐。
就目前來看,我們暫時(shí)可以得出的結(jié)論是罢荡,copy的作用僅僅是把指針指向同一塊內(nèi)存地址赡突,是淺復(fù)制,那么如果有其他手段能夠改變該段內(nèi)存的內(nèi)容柠傍,那么用copy消息返回的對象的值(所指內(nèi)存的內(nèi)容)也會跟著原本被“復(fù)制”的對象而改變麸俘。而mutableCopy的作用則會新開辟一段內(nèi)存,讓對象指向該段內(nèi)存惧笛,從而實(shí)現(xiàn)復(fù)制从媚,如果被復(fù)制的對象內(nèi)容改變,新對象的內(nèi)容并不會跟著改變(由于指向的不是同一段內(nèi)存)患整。
接下來再討論可變非集合類型的對象(這里用NSMutableString作為例子)拜效,先上代碼
- 實(shí)驗(yàn)代碼:
NSMutableString *strA = [NSMutableString stringWithFormat:@"strA"];
NSString *copyStr = [strA copy];
NSString *copyStr2 = [strA copy];
NSMutableString *copyMStr = [strA copy];
NSMutableString *copyMStr2 = [strA copy];
NSString *mutableCopyStr = [strA mutableCopy];
NSString *mutableCopyStr2 = [strA mutableCopy];
NSMutableString *mutableCopyMStr = [strA mutableCopy];
NSMutableString *mutableCopyMStr2 = [strA mutableCopy];
NSLog(@"strA -- %p &strA -- %p", strA, &strA);
NSLog(@"copyStr -- %p ©Str -- %p", copyStr, ©Str);
NSLog(@"copyStr2 -- %p ©Str2 -- %p", copyStr2, ©Str2);
NSLog(@"copyMStr -- %p ©MStr -- %p", copyMStr, ©MStr);
NSLog(@"copyMStr2 -- %p ©MStr2 -- %p", copyMStr2, ©MStr2);
NSLog(@"mutableCopyStr -- %p &mutableCopyStr -- %p", mutableCopyStr, &mutableCopyStr);
NSLog(@"mutableCopyStr2 -- %p &mutableCopyStr2 -- %p", mutableCopyStr2, &mutableCopyStr2);
NSLog(@"mutableCopyMStr -- %p &mutableCopyMStr -- %p", mutableCopyMStr, &mutableCopyMStr);
NSLog(@"mutableCopyMStr2 -- %p &mutableCopyMStr2 -- %p", mutableCopyMStr2, &mutableCopyMStr2);
- 運(yùn)行結(jié)果:
strA -- 0x600001c093e0 &strA -- 0x7ffeebd531d8
copyStr -- 0xd4d03e2a99e5492a ©Str -- 0x7ffeebd531d0
copyStr2 -- 0xd4d03e2a99e5492a ©Str2 -- 0x7ffeebd531c8
copyMStr -- 0xd4d03e2a99e5492a ©MStr -- 0x7ffeebd531c0
copyMStr2 -- 0xd4d03e2a99e5492a ©MStr2 -- 0x7ffeebd531b8
mutableCopyStr -- 0x600001c08930 &mutableCopyStr -- 0x7ffeebd531b0
mutableCopyStr2 -- 0x600001c08a50 &mutableCopyStr2 -- 0x7ffeebd531a8
mutableCopyMStr -- 0x600001c08f30 &mutableCopyMStr -- 0x7ffeebd531a0
mutableCopyMStr2 -- 0x600001c09290 &mutableCopyMStr2 -- 0x7ffeebd53198
結(jié)果分析:
- 同樣,觀察每個(gè)指針自身的地址各不相同各谚,說明也是生成了各個(gè)不同的指針紧憾,符合邏輯。觀察使用copy消息的4個(gè)變量昌渤,發(fā)現(xiàn)無論是NSString還是NSMutableString赴穗,只要是copy消息返回的都是與原字符串A不同指向的地址。但是返回的這些指針,都是指向同一塊新的內(nèi)存地址(0xd4d03e2a99e5492a)般眉。當(dāng)字符串A是可變字符串了赵,copy消息返回了新的對象,開辟了新的內(nèi)存甸赃,這些對象都會指向新開辟的這一段內(nèi)存柿汛。根據(jù)前面描述的,這種復(fù)制應(yīng)該是深復(fù)制埠对,這與上一個(gè)例子的非可變類型有所區(qū)別络断。
- 對于mutableCopy,與copy的情況有類似的地方项玛,都是返回的是與原字符串A不同指向的地址貌笨。不同的是,這些指針指向的卻是不是同一塊內(nèi)存地址稍计,無論我們定義的是NSString或者是NSMutableString躁绸,每個(gè)新的對象都是指向一塊全新的地址。不過我們目前只考慮是否開辟了新的內(nèi)存臣嚣,所以這種情況也是認(rèn)為是深復(fù)制净刮。
結(jié)論2:
- | 非可變非集合類型 | 可變非集合類型 |
---|---|---|
copy | 淺復(fù)制 | 深復(fù)制 |
mutableCopy | 深復(fù)制 | 深復(fù)制 |
集合類型對象
集合類型對象應(yīng)該是我們開發(fā)過程中最常用到的結(jié)構(gòu)之一,比如NSArray硅则,NSDictionary等淹父。那么對于集合類型的對象,我們向他們發(fā)送copy和mutableCopy消息時(shí)怎虫,又會產(chǎn)生何種效果暑认?
首先還是討論不可變的情況,這里以NSDictionary作為例子(選擇字典作為例子是想更全面的研究字典中的Key和Value出現(xiàn)的情況是否相同大审,數(shù)組則體現(xiàn)不出這個(gè)效果)蘸际。
- 實(shí)驗(yàn)代碼:
NSDictionary *aDic = @{@"aaa": @"111"};
NSDictionary *copyDic = [aDic copy];
NSDictionary *copyDic2 = [aDic copy];
NSMutableDictionary *copyMDic = [aDic copy];
NSMutableDictionary *copyMDic2 = [aDic copy];
NSDictionary *mutableCopyDic = [aDic mutableCopy];
NSDictionary *mutableCopyDic2 = [aDic mutableCopy];
NSMutableDictionary *mutableCopyMDic = [aDic mutableCopy];
NSMutableDictionary *mutableCopyMDic2 = [aDic mutableCopy];
NSLog(@"aDic -- %p aDic[@\"aaa\"] -- %p key aaa -- %p", aDic, aDic[@"aaa"], [[aDic allKeys] firstObject]);
NSLog(@"copyDic -- %p copyDic[@\"aaa\"] -- %p key aaa -- %p", copyDic, copyDic[@"aaa"], [[copyDic allKeys] firstObject]);
NSLog(@"copyDic2 -- %p copyDic2[@\"aaa\"] -- %p key aaa -- %p", copyDic2, copyDic2[@"aaa"], [[copyDic2 allKeys] firstObject]);
NSLog(@"copyMDic -- %p copyMDic[@\"aaa\"] -- %p key aaa -- %p", copyMDic, copyMDic[@"aaa"], [[copyMDic allKeys] firstObject]);
NSLog(@"copyMDic2 -- %p copyMDic2[@\"aaa\"] -- %p key aaa -- %p", copyMDic2, copyMDic2[@"aaa"], [[copyMDic2 allKeys] firstObject]);
NSLog(@"mutableCopyDic -- %p mutableCopyDic[@\"aaa\"] -- %p key aaa -- %p", mutableCopyDic, mutableCopyDic[@"aaa"], [[mutableCopyDic allKeys] firstObject]);
NSLog(@"mutableCopyDic2 -- %p mutableCopyDic2[@\"aaa\"] -- %p key aaa -- %p", mutableCopyDic2, mutableCopyDic2[@"aaa"], [[mutableCopyDic2 allKeys] firstObject]);
NSLog(@"mutableCopyMDic -- %p mutableCopyMDic[@\"aaa\"] -- %p key aaa -- %p", mutableCopyMDic, mutableCopyMDic[@"aaa"], [[mutableCopyMDic allKeys] firstObject]);
NSLog(@"mutableCopyMDic2 -- %p mutableCopyMDic2[@\"aaa\"] -- %p key aaa -- %p", mutableCopyMDic2, mutableCopyMDic2[@"aaa"], [[mutableCopyMDic2 allKeys] firstObject]);
- 運(yùn)行結(jié)果:
aDic -- 0x6000027a1e00 aDic[@"aaa"] -- 0x1042da820 key aaa -- 0x1042da800
copyDic -- 0x6000027a1e00 copyDic[@"aaa"] -- 0x1042da820 key aaa -- 0x1042da800
copyDic2 -- 0x6000027a1e00 copyDic2[@"aaa"] -- 0x1042da820 key aaa -- 0x1042da800
copyMDic -- 0x6000027a1e00 copyMDic[@"aaa"] -- 0x1042da820 key aaa -- 0x1042da800
copyMDic2 -- 0x6000027a1e00 copyMDic2[@"aaa"] -- 0x1042da820 key aaa -- 0x1042da800
mutableCopyDic -- 0x6000027a16e0 mutableCopyDic[@"aaa"] -- 0x1042da820 key aaa -- 0x1042da800
mutableCopyDic2 -- 0x6000027a1d40 mutableCopyDic2[@"aaa"] -- 0x1042da820 key aaa -- 0x1042da800
mutableCopyMDic -- 0x6000027a1920 mutableCopyMDic[@"aaa"] -- 0x1042da820 key aaa -- 0x1042da800
mutableCopyMDic2 -- 0x6000027a0ee0 mutableCopyMDic2[@"aaa"] -- 0x1042da820 key aaa -- 0x1042da800
結(jié)果分析:
- 情況和非集合類型相似,對于被復(fù)制的對象是不可變集合類型徒扶,當(dāng)向?qū)ο蟀l(fā)送copy消息時(shí)粮彤,無論新定義的對象是可變還是不可變的,返回的總是和原對象所指向的地址一樣的地址姜骡,同時(shí)导坟,字典里無論是key還是value都是和原對象是一致的。這還是比較符合認(rèn)知圈澈,既然所有的指針都指向同一個(gè)地址惫周,證明這里copy消息只是做了一個(gè)淺復(fù)制,既然是淺復(fù)制康栈,本質(zhì)上并沒有真正“復(fù)制”一個(gè)新的對象出來(沒有開辟新的內(nèi)存地址)递递,而是只是簡單地把新的對象指向了原來對象的地址喷橙,所以沒有新的字典和內(nèi)容生成,故所有地址都是一致的漾狼。
- 當(dāng)向?qū)ο蟀l(fā)送mutableCopy消息時(shí)重慢,無論新定義的對象是可變還是不可變的饥臂,返回的總是一段全新的內(nèi)存逊躁,指向全新的地址,不過有趣的是隅熙,字典里的key和value也是像前面copy的情況一樣稽煤,都是和原對象是一致。這就有點(diǎn)奇妙了囚戚,討論到現(xiàn)在酵熙,對于復(fù)制我們討論到的就兩種復(fù)制,淺復(fù)制和深復(fù)制驰坊,區(qū)分它們的方法就是看是否有開辟新的內(nèi)存匾二,但現(xiàn)在的情況是,mutableCopy確實(shí)是返回了新開辟內(nèi)存的新的字典對象拳芙,但字典里面的內(nèi)容卻和原對象的是指向同一塊地址察藐,也就是說如果通過某些手段改變了這些地址的值,所有這些新的字典對象的key和value也會隨之改變舟扎。這還是跟我們理解中的復(fù)制有點(diǎn)區(qū)別分飞。所以這里回應(yīng)到一開始提到的深復(fù)制也有兩種情況,一種是單單給對象開辟新的內(nèi)存睹限,另一種是 不僅給新的對象開辟內(nèi)存譬猫,而且會對其里面包含的內(nèi)容開辟新的地址,徹底復(fù)制一份全新的獨(dú)立的拷貝 羡疗,我們把后者這種深復(fù)制叫做 兩層深復(fù)制 (two-layer-copy)染服,同時(shí)為了區(qū)分,現(xiàn)在我們把第一種的深復(fù)制叫做 單層深復(fù)制叨恨。
接下來把可變集合類型也測試一下柳刮。
- 實(shí)驗(yàn)代碼
NSMutableDictionary *aDic = [NSMutableDictionary dictionaryWithCapacity:10];
[aDic setObject:@"111" forKey:@"aaa"];
NSDictionary *copyDic = [aDic copy];
NSDictionary *copyDic2 = [aDic copy];
NSMutableDictionary *copyMDic = [aDic copy];
NSMutableDictionary *copyMDic2 = [aDic copy];
NSDictionary *mutableCopyDic = [aDic mutableCopy];
NSDictionary *mutableCopyDic2 = [aDic mutableCopy];
NSMutableDictionary *mutableCopyMDic = [aDic mutableCopy];
NSMutableDictionary *mutableCopyMDic2 = [aDic mutableCopy];
NSLog(@"aDic -- %p aDic[@\"aaa\"] -- %p key aaa -- %p", aDic, aDic[@"aaa"], [[aDic allKeys] firstObject]);
NSLog(@"copyDic -- %p copyDic[@\"aaa\"] -- %p key aaa -- %p", copyDic, copyDic[@"aaa"], [[copyDic allKeys] firstObject]);
NSLog(@"copyDic2 -- %p copyDic2[@\"aaa\"] -- %p key aaa -- %p", copyDic2, copyDic2[@"aaa"], [[copyDic2 allKeys] firstObject]);
NSLog(@"copyMDic -- %p copyMDic[@\"aaa\"] -- %p key aaa -- %p", copyMDic, copyMDic[@"aaa"], [[copyMDic allKeys] firstObject]);
NSLog(@"copyMDic2 -- %p copyMDic2[@\"aaa\"] -- %p key aaa -- %p", copyMDic2, copyMDic2[@"aaa"], [[copyMDic2 allKeys] firstObject]);
NSLog(@"mutableCopyDic -- %p mutableCopyDic[@\"aaa\"] -- %p key aaa -- %p", mutableCopyDic, mutableCopyDic[@"aaa"], [[mutableCopyDic allKeys] firstObject]);
NSLog(@"mutableCopyDic2 -- %p mutableCopyDic2[@\"aaa\"] -- %p key aaa -- %p", mutableCopyDic2, mutableCopyDic2[@"aaa"], [[mutableCopyDic2 allKeys] firstObject]);
NSLog(@"mutableCopyMDic -- %p mutableCopyMDic[@\"aaa\"] -- %p key aaa -- %p", mutableCopyMDic, mutableCopyMDic[@"aaa"], [[mutableCopyMDic allKeys] firstObject]);
NSLog(@"mutableCopyMDic2 -- %p mutableCopyMDic2[@\"aaa\"] -- %p key aaa -- %p", mutableCopyMDic2, mutableCopyMDic2[@"aaa"], [[mutableCopyMDic2 allKeys] firstObject]);
- 運(yùn)行結(jié)果:
aDic -- 0x600002f53240 aDic[@"aaa"] -- 0x10709f800 key aaa -- 0x10709f820
copyDic -- 0x600002f53220 copyDic[@"aaa"] -- 0x10709f800 key aaa -- 0x10709f820
copyDic2 -- 0x600002f531a0 copyDic2[@"aaa"] -- 0x10709f800 key aaa -- 0x10709f820
copyMDic -- 0x600002f52d20 copyMDic[@"aaa"] -- 0x10709f800 key aaa -- 0x10709f820
copyMDic2 -- 0x600002f52f00 copyMDic2[@"aaa"] -- 0x10709f800 key aaa -- 0x10709f820
mutableCopyDic -- 0x600002f52de0 mutableCopyDic[@"aaa"] -- 0x10709f800 key aaa -- 0x10709f820
mutableCopyDic2 -- 0x600002f52d60 mutableCopyDic2[@"aaa"] -- 0x10709f800 key aaa -- 0x10709f820
mutableCopyMDic -- 0x600002f52e00 mutableCopyMDic[@"aaa"] -- 0x10709f800 key aaa -- 0x10709f820
mutableCopyMDic2 -- 0x600002f52e60 mutableCopyMDic2[@"aaa"] -- 0x10709f800 key aaa -- 0x10709f820
結(jié)果分析:
- 情況有點(diǎn)出乎我預(yù)料,本來以為是跟非集合類型(NSString)類似特碳,用copy消息返回的是一段新開辟的內(nèi)存(深復(fù)制)诚亚,所有4個(gè)指針都是指向那段新開的內(nèi)存。但現(xiàn)實(shí)的情況確實(shí)每個(gè)對象各自指向了不同的新的內(nèi)存午乓,盡管都是深復(fù)制站宗。而且這里提到的深復(fù)制,是我們上述的單層深復(fù)制益愈,因?yàn)榭梢钥闯鲎值涞腒ey和value都是跟原來的字典一樣的梢灭。
- 同時(shí)夷家,mutableCopy就和之前的完全一致了,每個(gè)對象都各自開辟了新的互不相同的內(nèi)存敏释,然而字典內(nèi)容也還是和原字典的一致库快,同樣也是單程深復(fù)制。
結(jié)論3:
- | 不可變集合類型 | 可變集合類型 |
---|---|---|
copy | 淺復(fù)制 | 單層深復(fù)制 |
mutableCopy | 單層深復(fù)制 | 單層深復(fù)制 |
彩蛋:在一開始的時(shí)候钥顽,我是把key和value都設(shè)置成@“aaa”义屏,運(yùn)行后發(fā)現(xiàn)key和value的地址都是一樣的,這不就是淺復(fù)制嗎蜂大?猜測系統(tǒng)這樣的做法是為了節(jié)省內(nèi)存吧闽铐?
最終結(jié)論
所以,我們可以得出:
對于不可變的非集合類對象進(jìn)行 copy 操作奶浦,其內(nèi)存地址并沒有發(fā)生變化兄墅,屬于淺復(fù)制;進(jìn)行 mutableCopy 操作澳叉,內(nèi)存地址發(fā)生了變化隙咸,深復(fù)制。
對于不可變的集合類對象進(jìn)行 copy 操作成洗,其內(nèi)存地址并沒有發(fā)生變化五督,屬于淺復(fù)制;進(jìn)行 mutableCopy 操作泌枪,內(nèi)存地址發(fā)生了變化概荷,但是其中的內(nèi)容的內(nèi)存地址并沒有發(fā)生變化,屬于單層深復(fù)制碌燕。
對于可變集合類對象误证,不管是進(jìn)行 copy 操作還是 mutableCopy 操作,其內(nèi)存地址都發(fā)生了變化修壕,但是其中的元素內(nèi)存地址都沒有發(fā)生變化愈捅,屬于單層深復(fù)制。