Swift & Objective-C

Swift Copy-on-Write:値型がプロトタイプパターンを置き換える仕組み(まとめ)

Swift Copy-on-Writeは、値型を代入するとストレージを共有し、一方が変更された瞬間にだけ実際のコピーを行います。この最適化が値の意味を保ちながら、プロトタイプパターンの役割を小さくする仕組みを解説します。

読了 6 分
Swift Copy-on-Write:値型がプロトタイプパターンを置き換える仕組み(まとめ)のカバー画像

Objective-CやJavaを先に学んでからSwiftに移ると、必ず一度は引っかかるポイントがあります。

それは「オブジェクトを複製するにはどうすればいいのか?」という疑問です。

clone()のようなメソッドを探し回りがちですが、Swiftではその必要はほとんどありません。

結論からお話しします。

Swiftは、構造体や配列などの値型とCopy-on-Write(コピーを遅延させる最適化)によって、デザインパターンでいう「プロトタイプパターン」を言語レベルで置き換えます。

オブジェクトを複製するために別のパターンを設計する必要はなく、値を代入するだけで安全なコピーが得られます。

この記事では、プロトタイプパターンが本来何を解決しようとしたのか、Swiftの値型とCopy-on-Writeがそれをどのように自然に取り込むのかを、例とともに整理します。

コピーの種類を区別するには、まず浅いコピー vs 深いコピーが基礎になります。クラスの複製まで必要な場合は、SwiftのプロトタイプパターンとNSCopyingがもう一つの選択肢を示します。


プロトタイプパターンが本来解決しようとした問題

プロトタイプパターンはGoFデザインパターンの生成パターンに属します。

要点は簡単です。新しいオブジェクトを最初から作る代わりに、既存のオブジェクトを複製して新しいインスタンスを得ます。

なぜこのような仕組みが必要だったのでしょうか?

Objective-CやJavaなどの言語では、オブジェクトの多くが参照型です。

オブジェクトを変数に代入すると、値ではなく同じオブジェクトを指すアドレスだけがコピーされます。

そのため、Aを変更するとBも一緒に変わってしまいます。

これを避けるため、開発者はclone()copy()メソッドを自分で作り、「本当のコピー」を返すようにしました。

プロトタイプパターンは、こうした状況のための道具です。

  • オブジェクトの生成コストが高く、似たオブジェクトが複数必要なとき
  • 元のオブジェクトに触れず、独立したコピーが必要なとき
  • 複製ロジックをオブジェクト自身に担当させたいとき

Swiftの値型:代入するだけでコピーが完了

Swiftの構造体(struct)と列挙型(enum)、そしてArray・Dictionary・Stringなどの標準型はすべて値型です。

値型は代入した瞬間、または関数に渡した瞬間にコピーされます。

つまり、言語が自動的にコピーを作ってくれます。

例を見ると、より明確になります。

struct Point { var x: Int; var y: Int }

var a = Point(x: 1, y: 2)
var b = a      // この瞬間に値がコピーされる
b.x = 99
// a.xは引き続き 1, b.xだけを 99

b = aこの1行が、プロトタイプパターンのclone()が担っていた役割を代わりに果たします。

別の複製メソッドも、コピー用プロトコルの採用も必要ありません。

元のaは安全に保護され、bは完全に独立したコピーになります。

オブジェクト同士が絡み合うことで起きる厄介なバグが、そもそも発生しない構造なのです。


Copy-on-Writeがパフォーマンス問題を解決する方法

ここで自然な疑問が生まれます。

「代入するたびに丸ごとコピーしたら、大きな配列は遅くなりすぎないでしょうか?」

その指摘はもっともです。そこでSwiftが使う最適化がCopy-on-Write、略してCoWです。

CoWの原理は次のとおりです。

値を代入すると、まず内部ストレージを共有します。実際のデータコピーは後回しにします。

その後、どちらかが値を変更しようとした瞬間に、初めて実際のコピーが行われます。

触れるまでは共有し、触れた瞬間に分かれる
触れるまでは共有し、触れた瞬間に分かれる
タイミング 内部動作 コスト
代入時 ストレージを共有(参照のみ) 非常に低い
読むだけのとき 共有を維持 コピーなし
値を変更するとき この時点で実際にコピー この時点でのみ発生

これにより、開発者は値型の安全性を享受しながら、不要なコピーコストを負わずに済みます。

読むだけで変更しなければ、コピーはまったく発生しないからです。

この概念を理解してから、複製について悩むことが大幅に減りました
この概念を理解してから、複製について悩むことが大幅に減りました

Array、Dictionary、Set、Stringなどの標準ライブラリ型には、すでにこのCoWが組み込まれています。

自分で実装しなくても、言語と標準ライブラリが自動的に処理してくれます。


それでもプロトタイプパターンが必要になる場合はあるのでしょうか?

「では、Swiftではプロトタイプパターンは完全に廃れた概念なのでしょうか?」

必ずしもそうではありません。

クラス(class)が必要な状況があります。参照セマンティクスが不可欠な場合や、Objective-Cとの相互運用が必要な場合、継承が必要な場合です。

このようなときにクラスインスタンスの独立したコピーが必要なら、今でも複製ロジックを自分で作る必要があります。

SwiftにはNSCopyingプロトコルとcopy()があり、これが実質的にプロトタイプパターンの伝統的な形です。

次のように分けて考えると分かりやすいでしょう。

  • 構造体・値型を使う → 代入だけでコピー完了、プロトタイプパターンは不要
  • クラスを必ず使う → 必要に応じて複製ロジックを自分で実装、ここでパターンが生き残る

そのため、Swiftコミュニティでは「まず値型を検討する」とよく助言されます。

値型を基本にすれば、複製の問題そのものがなくなるからです。


まとめ

慣れない言語のデザインパターンを無理に持ち込むと、かえってコードが複雑になることがあります。

Swiftでは値型とCopy-on-Writeがプロトタイプパターンの役割を自然に埋めてくれることを覚えておくだけで、複製に関する悩みはずっと軽くなります。

次にオブジェクトをコピーすることになったら、clone()を探す前に「これ、構造体に変えられないかな?」と考えてみてください。きっと役に立ちます。

あわせて読みたい