先來點基礎的
深拷貝和淺拷貝
淺拷貝
- 指針拷貝,源對象和副本指向的是同一個對象
- 對象的引用計數器+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 如何使用等等,後續相關的内容部落客會繼續更新,歡迎關注。