淺拷貝和深拷貝 iOS 的copy 以及 mutablecopy

-UP_UP-發表於2016-05-28

先來點基礎的

深拷貝和淺拷貝

淺拷貝

  • 指標拷貝,源物件和副本指向的是同一個物件
  • 物件的引用計數器+1,其實相當於做了一次retain操作

深拷貝

  • 內容拷貝,源物件和副本指向的是不同的兩個物件
  • 源物件引用計數器不變,副本計數器設定為1

如何理解

很多人對於深淺拷貝總有一些誤解,比如很多人都認為 iOS 的 copy 是淺拷貝,mutablecopy 是深拷貝,這是大大地錯誤的。
博主認為比較好的理解方式是
不要把深淺拷貝和任何的具體的實現方法(函式)畫上等號
也就是區分開概念和方式,結果和實現。深淺拷貝是概念、是執行後的結果,copy和mutableCopy等方法只是實現拷貝的途徑。
比如說,對於 iOS的 copy 方法

  • 如果原物件是一個不可變物件,那麼副本指向原地址(返回自己) –>結果是淺拷貝
  • 如果原物件是一個可變物件,那麼副本指向不同的地址(生成新物件) –> 結果是深拷貝

再有,
- 我們對一個 NSArray 做mutablecopy,會返回一個 新的地址,也就是新物件 –>這是一個深拷貝
- 我們對一個 NSString 做mutablecopy,會返回一個 新的陣列物件,但是陣列中的元素指標還是指向原來的地址。– >很顯然這仍然是一個 淺拷貝

所以,我們記住深淺拷貝的概念,並區分不同情境下使用不同的 copy 方法產生的結果是深拷貝還是淺拷貝就 OK 了。
在我們需要使用 copy 時,首先判斷我們需要深拷貝還是淺拷貝,然後根據情景選擇實現的方法,這樣就掌握了深淺拷貝。

iOS 的copy和mutableCopy

iOS中關於拷貝有兩個方法,copy和mutableCopy。

首先要糾正的一點是:
很多人都認為 iOS 的 copy 是淺拷貝,mutablecopy 是深拷貝,這是錯誤的。

copy

  • 需要實現NSCoppying協議
  • 不論原物件是否可變,建立的都是不可變副本(如NSString、NSArray、NSDictionary)
  • 如果原物件是一個不可變物件,那麼副本指向原地址(返回自己)
  • 如果原物件是一個可變物件,那麼副本指向不同的地址(生成新物件)

mutableCopy

  • 需要先實現NSMutableCopying協議
  • 不管原物件是否可變,建立的都是可變副本(如NSMutableString、NSMutableArray、NSMutableDictionary)
  • 不論原物件是否可變,副本都指向不同的地址,也就是都生成了新的物件

自定義物件的 copy

如果是我們定義的物件,那麼需要我們自己要實現NSCopying,NSMutableCopying,這樣就能呼叫copy和mutablecopy了
- (id)copyWithZone:(NSZone*)zone

{

PersonObject*copy = [[[selfclass]allocWithZone:zone]init];

copy->name= [namecopy];

copy->imutableStr= [imutableStrcopy];

copy->age=age;

returncopy;

}

- (id)mutableCopyWithZone:(NSZone*)zone

{

PersonObject*copy =[[[selfclass]allocWithZone:zone]init];

copy.name= [self.namemutableCopy];

copy.age=age;

returncopy;

}

容器的拷貝

有時候我們會有陣列等容器物件的完全深拷貝,舉個例子:
當前的 tableview 使用資料來源 arry1,點選跳轉到下級頁面時還需要使用 arry1 的資料,但是有可能對其中的元素進行增刪改的操作,但是又不能影響arry1的元素,這時候我們就需要對arry1進行深拷貝:新的陣列物件以及陣列中的元素也都是新的。

但是我們知道 mutablecopy只會返回一個 新的陣列物件陣列中的元素指標還是指向原來的地址
那應該如何實現容器的深拷貝呢?

查閱官方文件:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Collections/Articles/Copying.html

官方描述

要獲取一個集合的深拷貝,有兩種方法

initWithArray:copyItems:

NSArray *deepCopyArray=[[NSArray alloc] initWithArray:someArray copyItems:YES];

只翻譯重要部分,其他的建議通讀上面地址裡的文件

However, copyWithZone: produces a shallow copy. This kind of copy is only capable of producing a one-level-deep copy. If you only need a one-level-deep copy, you can explicitly call for one as in Listing 2。
不管怎樣,copyWithZone:是一個淺拷貝。這種拷貝只能產生一個 一級 的拷貝。如果你只需要一個 一級深度 的拷貝,你可以選擇該方法。

NSKeyedUnarchiver

NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];

If you need a true deep copy, such as when you have an array of arrays, you can archive and then unarchive the collection, provided the contents all conform to the NSCoding protocol. An example of this technique is shown in Listing 3.
如果你需要一個真正的深拷貝,比如你有一個元素是陣列的陣列(二維陣列),你可以對集合進行歸檔然後再反歸檔,要讓內容都遵循NSCoding協議。這種情況下的一個例子就是上面的。

OK,關於深淺拷貝就先介紹到這裡,希望對大家在需要copy的情況下能起到一些幫助。
其實 iOS 中的深淺拷貝內容還是不少的,比如屬性中的 copy 如何使用等等,後續相關的內容博主會繼續更新,歡迎關注。

相關文章