Swift & Objective-C

Swiftのclassとstruct:コピー後も共有されるもの

値、クラス参照、参照を含む構造体を比較し、どの変更が伝わるかを実行して確認します。構造体は深いコピーやスレッド安全性を保証しません。

読了 3 分
Swiftのclassとstruct:コピー後も共有されるもののカバー画像

値を表すモデルではstructから検討し、同一オブジェクトの識別性や状態共有が必要ならclassを選びます。オブジェクトをstructで包んでも、独立性やスレッド安全性は自動で得られません。

仕組みと設計の判断基準

構造体の代入は格納された値をコピーします。プロパティがクラス参照なら、コピーされるのは参照であり、参照先は共有されます。深いコピーやスレッド安全性とは区別してください。継承やObjective-CのオブジェクトAPIも選択に関係します。 一次資料

再現コード

Pointの整数、Boxの参照、Boxを持つEnvelopeを順にコピーします。オブジェクトのプロパティ変更と、コピー側のboxを新しいオブジェクトへ置き換える操作を分けました。各段階で原本とコピーの値をpreconditionで確認します。

この独立したサンプルは2026-10-23に、arm64 macOS向けのApple Swift 6.4をSwift 6言語モードで実行しました。ネットワーク接続や追加パッケージは不要です。

Swiftファイル全体をダウンロード

ダウンロード先のフォルダーで実行してください。検証条件を満たさない場合はpreconditionで停止します。検証が削除されるため、-Ouncheckedは使用しないでください。

swift -swift-version 6 swift-class-vs-struct.swift
struct Point { var x: Int }
final class Box {
    var x: Int
    init(_ x: Int) { self.x = x }
}
struct Envelope { var box: Box }

let point = Point(x: 0)
var copiedPoint = point
copiedPoint.x = 10
precondition(point.x == 0 && copiedPoint.x == 10)

let box = Box(0)
let copiedReference = box
copiedReference.x = 10
precondition(box === copiedReference && box.x == 10)

let envelope = Envelope(box: Box(0))
var copiedEnvelope = envelope
copiedEnvelope.box.x = 20
precondition(envelope.box.x == 20)
copiedEnvelope.box = Box(30)
precondition(envelope.box.x == 20 && copiedEnvelope.box.x == 30)

print("struct=0,10 class=10,10 nested-reference=20,20 replacement=20,30")

実行結果と読み取り方

struct=0,10 class=10,10 nested-reference=20,20 replacement=20,30

Pointは0と10に分かれ、Boxへの参照は10を共有します。コピーしたEnvelopeからオブジェクトを変更すると両方が20になります。一方、コピー側のboxをBox(30)へ置き換えても原本は20のままです。

適用範囲と注意点

この逐次実行の実験はコピーの意味を確認するもので、競合や性能は測定していません。変更可能な変数や内部参照を共有するなら、隔離や同期の検討が必要です。Swift 6のSendable検査も、struct宣言だけと同義ではありません。

次に確認すること

モデルをコピーした後も一緒に変わるべき状態を列挙してください。意図しない参照共有には、不変の値や明示的な複製方針を検討します。大きな保存領域を効率よくコピーする話はCoWの実験に続きます。

関連する実験

出典と確認基準