天天看點

你不得不知道的iOS 中的 Copying

Copying 在 iOS 中有很多概念,例如淺拷貝與深拷貝、copy 與 mutableCopy、NSCopying 協定,一直想徹底搞明白這些概念,刨根問底不搞懂不罷休嘛。于是搜 Google 看了一些部落格,又去翻了 Apple 相關的文檔,發現網上許多部落格都了解錯了,下面說說自己的了解。

淺拷貝與深拷貝

對于淺拷貝(Swallow Copy)與深拷貝(Deep Copy),經常看到這樣的說法:淺複制是指針拷貝,僅僅拷貝指向對象的指針;深複制是内容拷貝,會拷貝對象本身。 這句話并沒有說錯,但需要注意的是指針/内容拷貝針對的是誰,無論淺拷貝還是深拷貝,被拷貝的對象都會被複制一份,有新的對象産生,而在複制對象的内容時,對于被拷貝對象中的指針類型的成員變量,淺拷貝隻是複制指針,而深拷貝除了複制指針外,會複制指針指向的内容。下面我們以 Apple 官方文檔中的圖檔進行說明:

你不得不知道的iOS 中的 Copying
對普通對象 ObjectA 進行 copy,無論淺拷貝還是深拷貝,都會複制出一個新的對象 ObjectB,隻是淺拷貝時 ObjectA 與 ObjectB 中的 textColor 指針還指向同一個 NSColor 對象,而深拷貝時 ObjectA 和 ObjectB 中的 textColor 指針分别指向各自的 NSColor 對象(NSColor 對象被複制了一份)。
你不得不知道的iOS 中的 Copying

對集合對象 Array1 進行 copy,無論淺拷貝還是深拷貝,都會複制出一個新的對象 Array2,隻是淺拷貝時 Array1 與 Array2 中各個元素的指針還指向同一個對象,而深拷貝時 Array1 和 Array2 中各個元素的指針分别指向各自的對象(對象被複制了一份)。

Copy 與 MutableCopy

在說明 copy 與 mutableCopy 之前,我們思考一下:拷貝的目的是什麼?在動态庫加載時,隻讀的 TEXT 段是被所有使用動态庫的程式共享的, 而可寫的 DATA 段會使用 COW(Copy On Write)技術,當某個程式需要修改 DATA 段時會拷貝一份,供此程式專用。是以,拷貝的目的主要用于拷貝一份新的資料進行修改,而不會影響到原有的資料。如果不修改,拷貝就沒有必要。

在 iOS 中,有一些系統類根據是否可變進行了區分,例如 NSString 與 NSMutableString,NSArray 與 NSMutableArray 等。為了在兩者之間進行轉換(我了解這是主要目的),NSObject 提供了 copy 與mutableCopy 方法, copy 複制後對象是不可變對象,mutableCopy 複制後對象是可變對象。對象有不可變對象和可變對象,複制方法有 copy 和 mutableCopy,是以存在四種情況:

  • 不可變對象 copy:對象是不可變的,再複制出一份不可變對象沒有意義,是以根本沒有發生任何拷貝,對象隻有一份。
  • 不可變對象 mutableCopy:可變對象的能夠修改,原來的不可變對象不支援,是以需要複制出一個新對象,是淺拷貝。
  • 可變對象 copy:不可變對象不能修改,原來的可變對象不支援,是以需要複制出新對象,是淺拷貝。
  • 可變對象 mutableCopy:可變對象的修改不應該影響到原來的可變對象,是以需要複制出新對象,是淺拷貝。

如何進行深拷貝呢?

對于集合類型的對象,将 initWithArray:copyItems: 第二個參數設定成 YES 時,會對集合内每一個元素發送 copyWithZone: 消息,元素進行複制,但是對于元素中指針類型的成員變量,依然是淺拷貝,是以這種拷貝被稱為單層深拷貝(one-level-deep copy)。

如果想進行完全的深拷貝,可以先通過 NSKeyedArchiver 将對象歸檔,再通過 NSKeyedUnarchiver 将對象解歸檔。由于在歸檔時,對象中每個成員變量都會收到 encodeWithCoder: 消息,相當于将對象所有的資料均序列化儲存到磁盤上(可以看成換了種資料格式的拷貝),再通過 initWithCoder: 解歸檔時,就将拷貝過的資料經過轉換後讀取出來,深拷貝。

NSCopying

如果自定義的類也想要支援 copy 和 mutableCopy 方法,就需要實作 NSCopying 和NSMutableCopying 協定。在實作 copyWithZone: 方法時需要注意:

  • copyWithZone: 相當于新建立一個對象,并将目前對象的值複制到新建立的對象中。設定時應直接通路成員變量而不是通過屬性通路。
  • 直接從 NSObject 繼承的類,應使用 [[[self class] allocWithZone:zone] init],使得在建立新對象時能夠使用正确的類。
  • 父類中已經實作了 copyWithZone: 時,應先調用父類的方法,讓父類建立對應的對象(self class 能保證建立對象是正确的),并拷貝父類中定義的成員變量。

- (id)copyWithZone:(NSZone *)zone {

YourClass *object = [

super

copyWithZone:zone];

_property = xxx;

return

object;

}

繼續閱讀