概要
最近Swiftを触っているときにふとSwiftにおけるシャローコピーとディープコピーってどういう仕様なんだろう?と思い、書いてみました。
詳細
用語の説明
まずはシャローコピーとディープコピーについての説明。
シャローコピー
コピーされたオブジェクトが参照しているオブジェクトへの新しい参照を作成すること。
コピー元とコピー先のコレクション(例:配列やディクショナリ)は同じオブジェクトを共有します。故に、内部を変更した場合、コピー元とコピー先の両方に反映される。
シャローは浅い、という意味でシャローコピーは浅いコピーとも呼ばれる。
参照
ディープコピー (Deep Copy)
コピーされたオブジェクトとその内部のオブジェクトのコピーを生成すること。コピー元とコピー先は完全に独立したオブジェクトであり、内部のオブジェクトも再帰的にコピーされる。故にコピー元のオブジェクトを変更した場合にコピー先のオブジェクトに変更は反映されない
ディープは深い、という意味でディープコピーは深いコピーとも呼ばれる。
Swiftでの仕様
この公式のブログを見る限り、Swiftでは単純に代入を行った場合、Int、String、Structなどの値型はディープコピー、参照型のclassはシャローコピーの原則がある。
それらを踏まえると、基本的には値型はそこまで気を遣って管理する必要はないが、classの管理については無闇矢鱈に共有代入を繰り返すことは避けた方が良いと思える。
ちなみに、classでシャローコピーとディープコピーをSwiftでそれぞれ実装した場合、以下のようになります。
import Foundation
// シャローコピーの例
class ShallowObject {
var value: Int
init(value: Int) {
self.value = value
}
}
let originalShallow = ShallowObject(value: 42)
let copiedShallow = originalShallow
// originalShallowとcopiedShallowは同じオブジェクトを参照
print(originalShallow.value) // 42
print(copiedShallow.value) // 42
// originalShallowの変更はcopiedShallowにも反映される
originalShallow.value = 99
print(copiedShallow.value) // 99
originalShallow
に対して行った変更がcopiedShallow
にも反映されていることが確認できます。
一方でディープコピーですが、以下のように書けます。
import Foundation
// ディープコピーの例
class DeepCopyObject: NSCopying {
var value: Int
init(value: Int) {
self.value = value
}
func copy(with zone: NSZone? = nil) -> Any {
// ディープコピーを手動で実装
let copy = DeepCopyObject(value: self.value)
return copy
}
}
let originalDeepCopy = DeepCopyObject(value: 42)
let copiedDeepCopy = originalDeepCopy.copy() as! DeepCopyObject
// originalDeepCopyとcopiedDeepCopyは別々のオブジェクト
print(originalDeepCopy.value) // 42
print(copiedDeepCopy.value) // 42
// originalDeepCopyの変更はcopiedDeepCopyには影響しない
originalDeepCopy.value = 99
print(copiedDeepCopy.value) // 42
以下のようなcopy()関数を定義し、コピーするときに使用することで、ディープコピーを行っている。
この関数を使用したオブジェクトは異なるオブジェクトとして存在するため、コピー元のオブジェクトに対して行った変更に影響受けないということが分かる。
func copy(with zone: NSZone? = nil) -> Any {
// ディープコピーを手動で実装
let copy = DeepCopyObject(value: self.value)
return copy
}
使われる場面
ここは個人的な意見になるが、じゃあそれぞれのコピーはどういう場面で使えば良いのか?と考えてみると、以下のように考察できるのではないか。
シャローコピー:
- メモリ効率を重視しない場合
- 内部のオブジェクトを共有していることが許容される場合
- 内部のオブジェクトが変更されても問題ない場合
ディープコピー:
- 内部のオブジェクトへの変更が元のオブジェクトに影響しないようにする場合(オブジェクトの完全なコピーが必要な場合)
- オブジェクトの構造が複雑であり、深い階層のコピーが必要な場合
コピー時の注意点
また、調べているうちに以下のような注意点も分かった。
- ディープコピーは性能上のコストが高いことがあるため、十分な注意が必要なこと
- 内部に循環参照がある場合、ディープコピーは無限ループに陥る可能性があること
- ディープコピーは深い階層まで再帰的に行われるため、大きなデータ構造ではコストが高いこと
- Swiftにおいて、
Array
やDictionary
のcopy()
メソッドはシャローコピーを行うこと
備考
Apple公式のSwiftのはじめ方というドキュメントが丁寧に解説されていて分かりやすかった。