Objective-C copy,看我就夠了

發表於2016-12-16

一、從物件導向到Objective-C概覽copy

1、物件導向:

In object-oriented programming, object copying is creating a copy of an existing object, a unit of data in object-oriented programming. The resulting object is called an object copy or simply copy of the original object. Copying is basic but has subtleties and can have significant overhead. There are several ways to copy an object, most commonly by a copy constructor or cloning. Copying is done mostly so the copy can be modified or moved, or the current value preserved. If either of these is unneeded, a reference to the original data is sufficient and more efficient, as no copying occurs.

在物件導向的程式設計中,物件的copy就是建立一個已經存在的物件的copy。這種物件的建立的結果被稱為原始物件的copy。copy是很基礎的,但是也有其精巧的地方,並且可能造成巨大的消耗。有很多種方式可以copy物件,最常用的就是copy構造器和克隆。copy經常用於物件的修改、移動和保護。如果上述的幾種應用都不需要,持有原始物件的引用就足夠了,並不需要copy。

2、OC:

In Objective-C, the methods copy and mutableCopy are inherited by all objects and intended for performing copies; the latter is for creating a mutable type of the original object. These methods in turn call the copyWithZone and mutableCopyWithZone methods, respectively, to perform the copying. An object must implement the corresponding copyWithZone method to be copyable.

在OC中,copy和mutableCopy兩個方法是被所有物件繼承的(有點小毛病,應該指所有繼承自NSObject的類),這兩個方法就是為copy準備的。其中,mutableCopy是為了建立原始物件的可變型別的copy。這兩個方法分別呼叫copyWithZone和mutableCopyWithZone兩個方法來進行copy。一個物件必須實現copyWithZone或者mutableCopyWithZone,才能進行copy或者mutableCopy。

那麼,我們可以從以上獲取到什麼資訊?

  • copy經常用於物件的修改、移動和保護。如果上述的幾種應用都不需要,持有原始物件的引用就足夠了,並不需要copy。
  • 一個類必須實現copyWithZone或者mutableCopyWithZone,才能進行copy或者mutableCopy。

下一階段,本文將展開講述OC中的copy相關資訊以及如何使用copy方法。

二、Objective-C中copy相關

1、OC中的copy相關內容

  • 在XCode 裡Foundation.framework下的Headers裡,也在系統裡找到原檔案:/System/Library/Frameworks/Foundation.framework/Versions/C/Headers/NSObject.h
  • 在/usr/include/objc 下面找到 runtime 的 NSObject.h
  • 修飾屬性的關鍵字copy

2、這裡需要注意的有以下幾點

  • 若想使用copymutableCopy,需要分別實現NSCopying協議和NSMutableCopying協議,即實現copyWithZone:mutableCopyWithZone:方法。
  • 繼承自NSObject的大部分框架類均預設實現了NSCopying,並且一些具備可變型別的類如NSString、NSArray、NSDictionary,以及它們的可變型別類NSMutableString、NSMutableArray和NSMutableDictionary也實現了NSMutableCopying。(查了大部分常用類,均實現了NSCopying,所以暫時這麼說吧,可能有人說NSNumber並沒有實現NSCopying,那你可以看一下它的父類NSValue,其實現了NSCopying)
  • 對於一些自定義類,需要自己實現NSCopying。具體方式且看下部分。

三、非容器物件的深淺copy

首先,我們談一下非容器物件的深淺copy,這些非容器物件,包含常用的NSString、NSNumber等,也包括我們自定義的一些非容器類的例項。下面分三個三面進行分析。

1、首先說說深淺copy

準則
淺copy:指標複製,不會建立一個新的物件。
深copy:內容複製,會建立一個新的物件。

此處,不進行過多的解釋,從下面的結果分析中,按例子來理解。

2、框架類的深淺copy

準則
探究框架類深copy還是淺copy,需要清楚的是該類如何實現的NSCopying和NSMutableCopy的兩個方法copyWithZone:和mutableCopyWithZone:。然而OC並不開源,並且本文這裡也不會進行原始碼的推測。
那麼,我們應該遵循怎樣一個原則呢?如下:

  • 對immutableObject,即不可變物件,執行copy,會得到不可變物件,並且是淺copy。
  • 對immutableObject,即不可變物件,執行mutableCopy,會得到可變物件,並且是深copy。
  • 對mutableObject,即可變物件,執行copy,會得到不可變物件,並且是深copy。
  • 對mutableObject,即可變物件,執行mutableCopy,會得到可變物件,並且是深copy。

程式碼

結果分析

3、自定義類的深淺copy

準則
對於一個我們自定義的型別,顯然比框架類容易操縱的多。此處就拿NSCopying舉例(因為從沒有自定義過具有可變型別的類,當然,如果有需要的話,也可以實現NSMutableCopying)。自定義的類就和2中的原則沒有半毛錢關係了,一切就看你怎麼實現NSCopying協議中的copyWithZone:方法。

程式碼

結果分析

四、容器物件的深淺copy

前文已經知道了深淺copy的區別,你也大致猜到了為什麼將容器物件拿出來作為一塊。對,因為容器中可能包含很多物件,而這些物件也需要區分深淺copy。往深裡說,容器中可能包含容器物件,那更是麻煩了。不要急,看下面,以NSArray的深淺copy為例,將容器的深淺copy分為四種。

1、淺copy

準則
容器的淺copy,符合三.2中的原則。
程式碼

結果分析

2、單層深copy

準則
容器的單層深copy,符合三.2中的原則(只是深copy變成了單層深copy)。這裡的單層指的是完成了NSArray物件的深copy,而未對其容器內物件進行處理。
程式碼

結果分析

3、雙層深copy

準則
容器的雙層深copy已經脫離了三.2中的原則。這裡的雙層指的是完成了NSArray物件和NSArray容器內物件的深copy(為什麼不說完全,是因為無法處理NSArray中還有一個NSArray這種情況)。
程式碼

結果分析

限制
initWithArray: copyItems:會使NSArray中元素均執行copy方法。這也是我在testArr中放入NSMutableArray和NSMutableString的原因。如果我放入的是NSArray或者NSString,執行copy後,只會發生指標複製;如果我放入的是未實現NSCopying協議的物件,呼叫這個方法甚至會crash。這裡,官方文件的描述有誤。

If the objects in the collection have adopted the NSCopying
protocol, the objects are deeply copied to the new collection, which is then the sole owner of the copied objects.

4、完全深copy

準則
如果想完美的解決NSArray巢狀NSArray這種情形,可以使用歸檔、解檔的方式。
程式碼

結果分析

限制
歸檔和解檔的前提是NSArray中所有的物件都實現了NSCoding協議。

五、拾遺

1、關鍵字copy

程式碼與結果

分析

  • 結果1為strong修飾的結果,可見 [mutableStr appendString:@”456″]修改mutableStr造成了self.str的改變,顯然不安全;結果2為copy修飾的結果,可見 [mutableStr appendString:@”456″]修改mutableStr未造成self.str的改變,顯然安全。(從記憶體地址的變化也可以看出來)
  • 這裡可以推測出,copy關鍵字是在str屬性的set方法裡面返回了mutableStr的copy,而strong關鍵字僅僅是返回了mutableStr。

2、深淺copy對引用計數的影響

淺copy,類似strong,持有原始物件的指標,會使retainCount加一。
深copy,會建立一個新的物件,不會對原始物件的retainCount變化。

3、可變和不可變

可變和不可變上文談的不是很多,因為本文認為這完全與NSCopying和NSMutableCopying的實現息息相關。當然,對於框架類,我們可以簡單的認為,copy方法返回的就是不可變物件,mutableCopy返回的就是可變物件。如果是自定義的類,就看你怎麼實現NSCopying和NSMutableCopying協議了。

4、copy和block

首先,MRR時代用retain修飾block會產生崩潰,因為作為屬性的block在初始化時是被存放在靜態區的,如果block內呼叫外部變數,那麼block無法保留其記憶體,在初始化的作用域內使用並不會有什麼影響,但一旦出了block的初始化作用域,就會引起崩潰。所有MRC中使用copy修飾,將block拷貝到堆上。
其次,在ARC時代,因為ARC自動完成了對block的copy,所以修飾block用copy和strong都無所謂。

5、strong和shallowCopy

這個問題困惑了很久,最後只能得出一個結論,淺copy和strong引用的區別僅僅是淺copy多執行一步copyWithZone:方法。

六、文獻

1、https://developer.apple.com/library/prerelease/content/documentation/Cocoa/Conceptual/Collections/Articles/Copying.html#//apple_ref/doc/uid/TP40010162-SW1
2、https://en.wikipedia.org/wiki/Object_copying

七、感謝

最後,感謝來自百度的@楊飛宇同學,和他的討論給了我很多靈感。也感謝大家的閱讀,希望對您有所幫助。如果有錯誤的地方或者不理解的地方,希望大家在評論區積極指出。如果對您有所幫助,希望給作者一個喜歡和關注,您的支援是我最核心的動力。

相關文章