深拷貝和淺拷貝這個問題在面試中常常被問到,而在實際開發中,只要稍有不慎,就會在這裡出現問題。尤其對於初學者來說,我們有必要來好好研究下這個概念。我會以實際代碼來演示,相關示例代碼上傳至 這裡 。
首先通過一句話來解釋:深拷貝就是內容拷貝,淺拷貝就是指針拷貝。
深拷貝就是拷貝出和原來僅僅是值一樣,但是內存地址完全不一樣的新的對象,創建後和原對象沒有任何關系。淺拷貝就是拷貝指向原來對象的指針,使原對象的引用計數+1,可以理解為創建了一個指向原對象的新指針而已,並沒有創建一個全新的對象。
(1)非容器類對象的深拷貝、淺拷貝
// 字符串是直接賦值的,右側如果是copy,那麼就是淺拷貝;右側如果是mutableCopy,那麼就是深拷貝。 NSString *string1 = @"helloworld"; NSString *string2 = [string1 copy]; // 淺拷貝 NSString *string3 = [string1 mutableCopy]; // 深拷貝 NSMutableString *string4 = [string1 copy]; // 淺拷貝 NSMutableString *string5 = [string1 mutableCopy]; // 深拷貝 NSLog(@"string1 = %d;string2 = %d",string1,string2); NSLog(@"string1 = %d;string3 = %d",string1,string3); NSLog(@"string1 = %d;string4 = %d",string1,string4); NSLog(@"string1 = %d;string5 = %d",string1,string5);
。
我這裡用%d格式化打印出字符串對象的內存地址。我這裡主要通過內存地址來判斷是深拷貝還是淺拷貝,在該案例中,無論是深拷貝還是淺拷貝,字符串對象的值都是一樣的,所以暫且就不打印值了,而只打印地址。這裡的原字符串是直接賦值的,可以發現,右側如果使用copy,那麼打印出的內存地址是一樣的,表示是淺拷貝,只拷貝出了指向原對象的指針,沒有創建出新的對象。如果右側使用mutableCopy,那麼打印出的不一樣的內存地址,表示創建了一個新的對象,是深拷貝。
簡單說明一下什麼是非容器類對象,像NSString,NSNumber這些不能包含其他對象的叫做非容器類。如NSArray和NSDictionary可以容納其他對象的叫做容器類對象。
做一個小小的總結:
-- 淺拷貝類似retain,引用計數對象+1.創建一個指針;
-- 深拷貝是真正意義上的拷貝,是創建一個新對象。copy屬性表示兩個對象內容相同,新的對象retain為1,與原對象的引用計數無關,原對象沒有改變。copy減少對象對上下文的依賴。
-- retain屬性表示兩個對象地址相同(建立一個指針,指針拷貝),內容當然相同,這個對象的retain值+1.也就是說,retain是指針拷貝,copy是內容拷貝。
(2)改變字符串的創建方式,使用stringWithFormat創建字符串,而不是直接賦值
// 結果同上。右側如果是copy,那麼就是淺拷貝;右側如果是mutableCopy,那麼就是深拷貝。 NSString *string1 = [NSString stringWithFormat:@"helloworld"]; NSString *string2 = [string1 copy]; // 淺拷貝 NSString *string3 = [string1 mutableCopy]; // 深拷貝 NSMutableString *string4 = [string1 copy]; // 淺拷貝 NSMutableString *string5 = [string1 mutableCopy]; // 深拷貝 NSLog(@"string1 = %d;string2 = %d",string1,string2); NSLog(@"string1 = %d;string3 = %d",string1,string3); NSLog(@"string1 = %d;string4 = %d",string1,string4); NSLog(@"string1 = %d;string5 = %d",string1,string5);
。
結果同(1)。因為都是不可變字符串,創建方式並不影響拷貝方式。所以可以得出結論,對於字符串這類非容器類對象,copy是淺拷貝,mutable是深拷貝。
(3)以上測試的都是不可變字符串,這裡來嘗試下可變字符串
// 如果是一個MutableString,那麼無論是copy,mutableCopy,都會創建一個新對象。 NSMutableString *string1 = [NSMutableString stringWithString:@"helloworld"]; NSString *string2 = [string1 copy]; // 深拷貝 NSString *string3 = [string1 mutableCopy]; // 深拷貝 NSMutableString *string4 = [string1 copy]; // 深拷貝 NSMutableString *string5 = [string1 mutableCopy]; // 深拷貝 NSLog(@"string1 = %d;string2 = %d",string1,string2); NSLog(@"string1 = %d;string3 = %d",string1,string3); NSLog(@"string1 = %d;string4 = %d",string1,string4); NSLog(@"string1 = %d;string5 = %d",string1,string5);
打印結果如下:
。
可以看到,對於MutableString,右側無論是copy還是mutableCopy,都會創建一個新對象,都是屬於深拷貝。
現在我們對以上代碼做一點修改:
// 如果是一個MutableString,那麼無論是copy,mutableCopy,都會創建一個新對象。 NSMutableString *string1 = [NSMutableString stringWithString:@"helloworld"]; NSString *string2 = [string1 copy]; // 深拷貝 NSString *string3 = [string1 mutableCopy]; // 深拷貝 NSMutableString *string4 = [string1 copy]; // 深拷貝 NSMutableString *string5 = [string1 mutableCopy]; // 深拷貝 NSLog(@"string1 = %d;string2 = %d",string1,string2); NSLog(@"string1 = %d;string3 = %d",string1,string3); NSLog(@"string1 = %d;string4 = %d",string1,string4); NSLog(@"string1 = %d;string5 = %d",string1,string5); // 增加以下代碼 [string4 appendString:@"MMMMMM"]; [string5 appendString:@"11111"]; NSLog(@"string4 = %@",string4); NSLog(@"string5 = %@",string5);
-- 對於可變對象的復制,都是深拷貝;
-- 可變對象copy後返回的對象是不可變的,mutableCopy後返回的對象是可變的。
(4)我們上面提到的都是系統類,如果是我們自定義的類,復制結果又是怎樣呢?我下面定義一個Person類,實現如下:
Person.h
#import@interface Person : NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) NSInteger age; @end
Person.m
#import "Person.h" @interface Person()@end @implementation Person // 對應copy方法 - (id)copyWithZone:(NSZone *)zone { Person *person = [[Person allocWithZone:zone] init]; person.name = self.name; // 這裡的self就是被copy的對象 person.age = self.age; return person; } - (id)mutableCopyWithZone:(NSZone *)zone { Person *person = [[Person allocWithZone:zone] init]; person.name = self.name; person.age = self.age; return person; } @end
然後編寫測試代碼如下:
// Person類必須實現copyWithZone和mutableCopyWithZone方法。 Person *person = [[Person alloc] init]; person.name = @"Jack"; person.age = 23; Person *copyPerson = [person copy]; // 深拷貝 Person *mutableCopyPerson = [person mutableCopy]; // 深拷貝 NSLog(@"person = %d;copyPerson = %d",person,copyPerson); NSLog(@"person = %d;mutableCopyPerson = %d",person,mutableCopyPerson);
。
可以看到無論是用copy還是mutableCopy,復制後都創建了一個新的對象。為什麼呢?因為我們本來就在copyWithZone: ,mutableCopyWithZone方法中創建了一個新對象啊,所以一點都不奇怪。
(5)從上述(4)中可以看到我的Person自定義對象在copy後是創建了一個全新的對象,那麼這個對象中的屬性呢?這些屬性是深拷貝還是淺拷貝呢?
NSMutableString *otherName = [[NSMutableString alloc] initWithString:@"Jack"]; Person *person = [[Person alloc] init]; person.name = otherName; person.age = 23; [otherName appendString:@" and Mary"]; NSLog(@"person.name = %@",person.name);
。
打印的結果是"Jack",而不是”Jack and Mary“. 說明在執行person.name = otherName;的時候是重新創建了一個字符串。但是請大家注意,這裡我name的屬性修飾符是copy,如果把copy改成strong會變成怎麼樣呢?
對,沒錯,name改為strong後答案就是”Jack and Mary“。所以在這裡我們可以得出以下小小的結論:
-- 因為這裡的otherName是可變的,person.name的屬性是copy,所以創建了新的字符串,屬於深拷貝;
-- 如果person.name設置為strong,那麼就是淺拷貝。因為strong會持有原來的對象,使原來的對象的引用計數+1,其實就是指針拷貝。
-- 當然,這裡用strong還是copy,取決於實際需求。
(6)與上面(5)類似,我現在修改代碼如下:
NSString *otherName = @"jack"; Person *person = [[Person alloc] init]; person.name = otherName; person.age = 23; NSLog(@"othername = %d;person.name = %d",otherName,person.name);
現修改代碼如下:
NSString *otherName = @"jack"; Person *person = [[Person alloc] init]; person.name = [otherName copy]; person.age = 23; NSLog(@"othername = %d;person.name = %d",otherName,person.name);
再次修改代碼如下:
NSString *otherName = @"jack"; Person *person = [[Person alloc] init]; person.name = [otherName mutableCopy]; person.age = 23; NSLog(@"othername = %d;person.name = %d",otherName,person.name);無論person.name是strong還是copy,都是內容拷貝。創建一個全新的對象。因為對不可變對象執行mutableCopy一般都是深拷貝,也沒問題。
(7)講了這麼多非容器對象,最後我們來講講容器對象Array。先講不可變的容器對象
NSArray *array01 = [NSArray arrayWithObjects:@"a",@"b",@"c", nil]; NSArray *copyArray01 = [array01 copy]; NSMutableArray *mutableCopyArray01 = [array01 mutableCopy]; NSLog(@"array01 = %d,copyArray01 = %d",array01,copyArray01); NSLog(@"array01 = %d,mutableCopyArray01 = %d",array01,mutableCopyArray01); NSLog(@"array01[0] = %d,array01[1] = %d,array01[2] = %d",array01[0],array01[1],array01[2]); NSLog(@"copyArray01[0] = %d,copyArray01[1] = %d,copyArray01[2] = %d",copyArray01[0],copyArray01[1],copyArray01[2]); NSLog(@"mutableCopyArray01[0] = %d,mutableCopyArray01[1] = %d,mutableCopyArray01[2] = %d",mutableCopyArray01[0],mutableCopyArray01[1],mutableCopyArray01[2]);
。
我們可以得出以下結論:
-- copyArray01和array01指向的是同一個對象,包括裡面的元素也是指向相同的指針。
-- mutableCopyArray01和array01指向的是不同的對象,但是裡面的元素指向相同的對象,mutableCopyArray01可以修改自己的對象。
--copyArray01是對array01的指針復制(淺復制),而mutableCopyArray01是內容復制。
----------------------------------------------------------------------------------------------------------------------------------------------------------------------打個分割線
上面的是不可變容器NSArray,這裡測試下可變容器NSMutableArray:
NSMutableArray *array01 = [NSMutableArray arrayWithObjects:@"a",@"b",@"c", nil]; NSArray *copyArray01 = [array01 copy]; NSMutableArray *mutableCopyArray01 = [array01 mutableCopy]; NSLog(@"array01 = %d,copyArray01 = %d",array01,copyArray01); NSLog(@"array01 = %d,mutableCopyArray01 = %d",array01,mutableCopyArray01); NSLog(@"array01[0] = %d,array01[1] = %d,array01[2] = %d",array01[0],array01[1],array01[2]); NSLog(@"copyArray01[0] = %d,copyArray01[1] = %d,copyArray01[2] = %d",copyArray01[0],copyArray01[1],copyArray01[2]); NSLog(@"mutableCopyArray01[0] = %d,mutableCopyArray01[1] = %d,mutableCopyArray01[2] = %d",mutableCopyArray01[0],mutableCopyArray01[1],mutableCopyArray01[2]);
。
可得結論如下:
-- 容器對象和非容器對象類似,可變對象復制(copy,mutableCopy)的都是一個新對象;不可變對象copy是淺復制,mutableCopy是深復制。
-- 對於容器而言,元素對象始終是指針復制。
以上涉及的也只是一部分,想要對深拷貝、淺拷貝有更為深入的了解,必須首先要對內存管理較為熟悉,然後在實際開發中不斷去實踐,才會隨心所欲。