shallow & Deep Copy

今天無聊,又快要離職了,來寫點心得,也是去面試時碰到的題目。

就是深層copy 和 淺copy

在Objective - C 中 像是NSDictionaryt, NSArray 等等 他們都可以實現COPY / MutableCopy 這個語法。

你按住Command 點進去會發現他們都實現了NSCopying 這個Protocol 。

也就是說~如果你在Objective - C中沒看到NSCopying的Protocol時,程式就會當掉

例如 [[UIColor redColor]copy];


在Xcode中,對於基本數據類型 int , Bool Float 等等是直接賦值 。

但是對於指標類的型態 就有深淺層拷貝的差別,跟在C++中基本差不多,指向同一對像,生成新對像。

Xcode 為了優化 對於不可變(immutable)的Object 進行COPY 預設的都是淺層拷貝。

例如

1
2
3
NSDictionary *firstDic = [[NSDictionary alloc]initWithObjectsAndKeys:@"hello", @"test",nil];
NSDictionary *secondDic= [firstDic copy];
NSLog(@"first Dict:%p,retain Count: %ld\n second Dict:%p, retain Count: %ld",firstDic,[firstDic retainCount],secondDic,[secondDic retainCount]);

輸出結果會是

first Dict:0x100115570,retain Count: 2

second Dict:0x100115570, retain Count: 2

兩個指針指向同一個地方,RetainCount +1 證明他是指向同一個對象

這也很好理解 調用copy 他會返回一個不可變的對象,原本的firstDic也是一個不可變,因為都是不可變,
所以就乾脆指向同一位址就可以了。 所以這邊的COPY和Retain是並無差別

如果把NSDictionary 改成NSMutableDictionary 的話,

1
2
3
NSMutableDictionary *firstDic = [[NSDictionary alloc]initWithObjectsAndKeys:@"hello", @"test",nil];
NSMutableDictionary *secondDic= [firstDic copy];
NSLog(@"test Dict:%p,retain Count: %ld\ndest Dict:%p, retain Count: %ld",firstDic,[firstDic retainCount],secondDic,[secondDic retainCount]);

輸出結果會發現RetainCount 兩個都變成 1 指向的位址也變成不同。

但是需注意的是這裡SecondDic雖然你宣告的一個NSMutableDictionary 可是copy返回的是一個不可變的,

所以如果你要對SecondDic做操作時,可能馬上就會Crash。

所以這邊NSMutableDictionary SecondDic 需改成NSDictionary secondDic 才行

或是調用Mutablecopy 這是會返回一個可變的Object 。

如果你的Class 是一個自定義的話,當你執行 copy 或 MutableCopy時一定馬上當,

你需先在@interface 加入protocol <NSCopying>
然後再m檔中

一一對你的屬性對深或淺的實現 例如下列方法

m檔中

實現 - (id)copyWithZone:(NSZone *)zone;

假如你的Class 名叫做 Family

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (id)copyWithZone:(NSZone *)zone;
{ //shallow Copy
/* Family *f = [[[self class]allocWithZone:zone]init];
f.name = _Name;
f.peopleCount = _peopleCount;*/
//Deep Copy
Family *f = [[[self class]allocWithZone:zone]init];
f.name = [_name copy]; //NSString
f.peopleCount = [_peopleCount copy]; //NSNumber
[_Name release];
[_peopleCount release];
return person;
}

可以NSLog一下 兩個family 和 family.name 的指針

會發現淺拷貝的變量指針是指向同一對象,也就是雖然他COPY了兩個物件,可是內容物還是指向同一塊空間

再看看深層拷貝, 就會發現family的name 兩個指針是不同的,也代表這是真正的實現了COPY這個定義

##但是
這樣是只針對常量而言,如果Array中包的是object呢?
當用了mutableCopy後,兩個array的指標是不一樣,但是數組內的內容指標還是指向同一個位址。 要如何解決呢?

有三種方法

  1. 用 [[]NSArray alloc]initWithArray:copyitems] 這個函數,他會copy數組內的每個元素調用copy函數,並把返回的id加入到新的數組中。
  2. 用archiver方式,NSArray *arr = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyArchiver archivedDataWithRootObject:oldArr]];
  3. 用category 實現自定義的copy

##結論

  • 在Foundation中,當我們COPY是一個immutable(不可變)對象時,它的作用相當於retain (Xcode 優化)

  • 當使用mutableCopy時,不管源本對像是可變或不可變,都會回傳一個可變的而且實行真正的Copy

  • 當copy一個可變對像時,返回是一個不可變的,但也同樣實現了真正的Copy