copy 的實現原理與深淺拷貝

首先,從copy開始說,簡而言之,copy的目的就是生成一個新的實例,然後把其成員都按原實例賦值。對於非指針型的成員,比如BOOL, int, float,這樣的賦值可以直接進行。但是對於指針型的數據,比如Objc中用到的對象,就有Deep Copy和Shallow Copy的區別——這個和在C++中的基本上是一樣的:是生成新的成員對象,或是指向同一成員對象。 

瞭解了這點以後,再看看Copy在 Objetive-C中的實現方式。如果要調用一個對象的copy方法,這個對象必須遵循NSCopying的協議。這個協議中規定了一個方法:- (id)copyWithZone: ( NSZone  * )zone;我們就是通過實現這個方法給對象提供拷貝的功能。對於很多現有類,如NSString,NSDictionary,。。。這個方法已經實 現。假設我們現在自定義了一個類,需要爲這個類提供拷貝的功能,就需要自己來動手寫CopyWithZone的方法:示例如下:

這個是自定義的類:

  1. @interface Product : NSObject <NSCopying>
  2. {
  3. NSString *productName;
  4. float price;
  5. id delegate;
  6. }

  7. @end
複製代碼

然後我們需要在Product類中實現NSCopying中的copyWithZone方法:

  1. - (id)copyWithZone:(NSZone *)zone
  2. {
  3. Product *copy = [[[self class] allocWithZone: zone]
  4. initWithProductName:[self productName]
  5. price:[self price]]; //注意這裏,我們使用了class的allocWithZone的方法創建了一個拷貝,這裏假定Product類中有一個 initWithProductName: price:的初始化方法。那麼這樣調用後就得到了一個Product的副本,而且name和price都已經設置好了

  6. [copy setDelegate:[self delegate]]; //這裏再設置delegate

  7. return copy; //返回副本
  8. }
複製代碼

那麼這樣,如果我們有一個product的實例, 假設爲product1,然後調用Product *product2 = [product1 copy];
就會使用我們上面寫的copyWithZone的方法創建一個product1的副本,然後賦值給product2。


這裏再以上面方法中的成員delegate爲例,解釋一下deep copy和shallow copy:

在 copyWithZone方法中,我們得到了一個新的product實例,但是delegate是個對象,所以在副本中,我們可以選擇創建一個新的 delegate對象(deep copy),或是指向同一個delegate(shallow copy)。這個就取決於Product類中的setDelegate:方法了。你可以選擇在setDelegate的時候,copy,也可以讓它們都指 向同一個對象(但是需要retain,原因可以自己思考一下),當然,簡單assign在某種情況下也是可以的。

假設在Product類中有setDelegate:方法,或是有delegate的property:

  1. - (void)setDelegate: (id)aDelegate
  2. {
  3. [delegate release];
  4. delegate = [delegate copy];
  5. }
複製代碼

這 樣就是一個深拷貝了,因爲使用了delegate的copy方法得到了一個delegate的副本。至於如何得到delegate的副本,就要看 delegate的copyWithZone方法的實現了,不在這個層面的考慮中。也就是說,copy總是一中“遞歸”的形式,從上到下,我們可以一層一 層的考慮。

  1. - (void)setDelegate: (id)aDelegate
  2. {
  3. [delegate release];
  4. delegate = [aDelegate retain];
  5. }
複製代碼

這樣操作後,delegate和aDelegate爲同一對象,但是爲了內存管理方面的要求,我們調用了retain來將reference count加了一。當然,如果不需要了,還可以直接賦值(assign):

  1. - (void)setDelegate: (id)aDelegate
  2. {
  3. delegate = aDelegate
  4. }
複製代碼

你可以把這個例子自己實現一下,然後用log打一打內存,這個結構就很明瞭了。

然後再說一下可變副本(mutable copy)和不可變副本(immutable copy):

可變和不可變的概念,我們之前通過NSDictionary和NSMutableDictionary的區別瞭解過。

一 般來說,如果我們的某個類需要區別對待這兩個功能——同時提供創建可變副本和不可變副本的話,一般在NSCopying協議規定的方法 copyWithZone中返回不可變副本;而在NSMutableCopying的mutableCopyWithZone方法中返回可變副本。然後調 用對象的copy和mutableCopy方法來得到副本。

舉個例子:

NSDictionary類已經遵循了NSCopying和NSMutableCopy的協議,也就是說我們可以調用它的copy和mutableCopy來得到不可變和可變的副本,程序如下:

  1. NSDictionary *testDict = [[NSDictionary alloc]initWithObjectsAndKeys:@”hello”, @”test”,nil];
  2. NSDictionary *destDict = [testDict copy];
  3. NSLog(@”test Dict:%p,retain Count: %dndest Dict:%p, retain Count: %d”,testDict,[testDict retainCount],destDict,[destDict retainCount]);
複製代碼

這個在我機器上的運行結果爲:

test Dict:0x11f220, retain Count: 2

dest Dict:0x11f220,retain Count: 2

看 起來,兩個dict指向了同一片內存區域,但是retainCount加了1。這點需要理解一下,因爲我們使用NSCopying方法要返回一個不可變對 象。而且原來的testDict也是不可變的,那麼這裏的“副本”也就沒多大意義了(這就如同使用字符串常量時,系統會爲我們優化,聲明瞭多個字符串,但 是都是常量,且內容相等,那麼系統就只爲我們申請一塊空間,這個道理是一樣的)。既然都不可變,那麼指向同一個空間就可以了。這裏的copy和 retain沒什麼區別。

我們使用copyWithZone的方法返回immutable的對象,而不管原來的是可變的或是不可變的。我們再看一下如下代碼:

  1. NSMutableDictionary *testDict = [[NSMutableDictionary alloc]initWithObjectsAndKeys:@”hello”, @”test”,nil];
  2. NSMutableDictionary *destDict = [testDict copy];
  3. NSLog(@”test Dict:%p,retain count:%dndest Dict:%p,retain count:%d”,testDict,[testDict retainCount],destDict,[destDict retainCount]);
  4. [destDict setObject:@"what" forKey:@"test2"];
複製代碼

NSMutableDictionary是可變的,該代碼在我機器上運行的結果爲:

test Dict:0x20dcc0,retain count:1

dest Dict:0×209120,retain count:1

*** -[NSCFDictionary setObject:forKey:]: mutating method sent to immutable object

可 以看到因爲我們調用了可變對象的copy方法,這個不像之前的例子中一樣,只是retain了一下。這裏的test dict和dest Dict已經是兩個對象了,但是,copyWithZone的方法返回的是不可變的對象,因此之後的setObject: forKey:方法會出現錯誤。

下面這樣改一下就OK了。

  1. NSMutableDictionary *testDict = [[NSMutableDictionary alloc]initWithObjectsAndKeys:@”hello”, @”test”,nil];
  2. NSMutableDictionary *destDict = [testDict mutableCopy];
  3. NSLog(@”test Dict:%p,retain count:%dndest Dict:%p,retain count:%d”,testDict,[testDict retainCount],destDict,[destDict retainCount]);
  4. [destDict setObject:@"what" forKey:@"test2"];
  5. NSLog(@”destDict:%@”,destDict);
複製代碼

運行結果爲:

test Dict:0×123550,retain count:1


dest Dict:0x10a460,retain count:1


因爲我們使用了mutableCopy來得到了一個可變副本。

Note:對於系統提供的所有既支持NSCopying,又支持NSMutableCopying的類。

copy方法,得到的是不可變對象,不管以前的是可變還是不可變。

mutableCopy方法,得到的是可變對象,不管以前的是可變還是不可變。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章