iOS 複製知識點總結

發表於2017-08-22

1、系統物件的複製

不管是集合類物件,還是非集合類物件,接收到copymutableCopy訊息時,都遵循以下準則:

  • copy返回immutable物件;所以,如果對copy返回值使用mutable物件介面就會crash;
  • mutableCopy返回mutable物件;

下圖詳細闡述了NSStringNSMutableStringNSArrayNSMutableArrayNSDictionaryNSMutableDictionary分別呼叫copymutableCopy方法後的結果:

112030896-5b744f044027a0c1

1.1 非集合類物件的copy與mutableCopy

系統非集合類物件指的是NSStringNSNumber之類的物件。下面看一下非集合類NSString物件拷貝的例子:

copy

通過檢視記憶體,可以看到stringCopystring的地址是一樣,進行了指標拷貝;而stringMCopy的地址和string不一樣,進行了內容拷貝。

mutableCopy

crash的原因就是copy返回的物件是immutable物件。

1.2 集合類物件的copy與mutableCopy

集合類物件是指NSArrayNSDictionaryNSSet之類的物件。下面看一下集合類NSArray物件使用copymutableCopy的一個例子:

copy

可以看到copyArrayarray的地址是一樣的,而mCopyArrayarray的地址是不同的。說明copy操作進行了指標拷貝,mutableCopy進行了內容拷貝。但需要強調的是:此處的內容拷貝,僅僅是拷貝array這個物件,array集合內部的元素仍然是指標拷貝。這和上面的非集合immutable物件的拷貝還是挺相似的,那麼mutable物件的拷貝會不會類似呢?我們繼續往下,

mutableCopy

檢視記憶體,如我們所料,copyArraymCopyArrayarray的記憶體地址都不一樣,說明copyArraymCopyArray都對array進行了內容拷貝。

2、自定義物件的複製

使用copymutableCopy複製物件的副本使用起來確實方便,那麼我們自定義的類是否可呼叫copymutableCopy方法來複制副本呢?我們先定義一個Person類,程式碼如下:

然後嘗試呼叫Personcopy方法來複制一個副本:

執行程式,將會發生崩潰,並輸出以下錯誤資訊:

上面的提示:Person找不到copyWithZone:方法。我們將複製副本的程式碼換成如下:

再次執行程式,程式同樣崩潰了,並輸出去以下錯誤資訊:

上面的提示:Person找不到mutableCopyWithZone:方法。

大家可能會覺得疑惑,程式只是呼叫了copymutableCopy方法,為什麼會提示找不到copyWithZone:mutableCopyWithZone:方法呢?其實當程式呼叫物件的copy方法來複制自身時,底層需要呼叫copyWithZone:方法來完成實際的複製工作,copy返回實際上就是copyWithZone:方法的返回值;mutableCopymutableCopyWithZone:方法也是同樣的道理。

那麼怎麼做才能讓自定義的物件進行copymutableCopy呢?需要做以下事情:

所以讓我們的Person類能夠複製自身,我們需要讓Person實現NSCopying協議;然後實現copyWithZone:方法:

執行之後發現我們實現了物件的複製:

122030896-af5a229f764d9214

同時需要注意的是如果物件中有其他指標型別的例項變數,且只是簡單的賦值操作:person.obj2 = self.obj2,其中obj2是另一個自定義類,如果我們修改obj2中的屬性,我們會發現複製後的person物件中obj2物件中的屬性值也變了,因為對於這個物件並沒有進行copy操作,這樣的複製操作不是完全的複製,如果要實現完全的複製,需要將obj2對應的類也要實現copy,然後這樣賦值:person.obj2 = [self.obj2 copy]。如果物件很多或者層級很多,實現起來還是很麻煩的。如果需要實現完全複製同樣還有另有一種方法,那就是歸檔:

這樣我們就實現了自定義物件的複製,需要指出的是如果重寫copyWithZone:方法時,其父類已經實現NSCopying協議,並重寫過了copyWithZone:方法,那麼子類重寫copyWithZone:方法應先呼叫父類的copy方法複製從父類繼承得到的成員變數,然後對子類中定義的成員變數進行賦值:

關於mutableCopy的實現與copy的實現類似,只是實現的是NSMutableCopying協議與mutableCopyWithZone:方法。對於自定義的物件,在我看來並沒有什麼可變不可變的概念,因此實現mutableCopy其實是沒有什麼意義的,在此就不詳細介紹了。

3、定義屬性的copy指示符

如下段程式碼,我們在定義屬性的時候使用了copy指示符:

使用如下程式碼來進行測試:

執行程式會崩潰,並且提示以下資訊:

這段錯誤提示不允許修改personname屬性,這是因為程式定義name屬性時使用了copy指示符,該指示符置頂呼叫setName:方法時(通過點語法賦值時,實際上是呼叫對應的setter方法),程式實際上會使用引數的副本對name實際變數複製。也就是說,setName:方法的程式碼如下:

copy方法預設是複製該物件的不可變副本,雖然程式傳入的NSMutableString,但程式呼叫該引數的copy方法得到的是不可變副本。因此,程式賦給Person物件的name例項變數的值依然是不可變字串。
注意:定義合成gettersetter方法時並沒有提供mutableCopy指示符。因此即使定義例項變數時使用了可變型別,但只要使用copy指示符,例項變數實際得到的值總是不可變物件。


參考文章:
iOS之物件複製
iOS的深複製與淺複製