Swiftにおいて値型(struct、enum、tuple)はvalue semanticsを持ちます。別の変数に渡したり代入したりすると、その内容がすべてコピーされ、独立したインスタンスが作成されます。これにより、参照型(class)に特有のshared stateに関する複雑さを避けることができます。
しかし、メモリ最適化のために、コレクション(例えばArray、Dictionary、Set)はcopy-on-write戦略を使用しています。これは、いずれかのインスタンスが変更されたときにのみコピーが行われることを意味します。
例:
var a = [1, 2, 3] var b = a b.append(4) print(a) // [1, 2, 3] print(b) // [1, 2, 3, 4]
ここで配列aは変わりません。最初は共通のストレージがありましたが、bを変更するとSwiftはデータの別のコピーを作ります。
重要な点は、構造体が参照型(例えばクラス)を含む場合、value semanticsは構造体自体にのみ適用され、内部の参照オブジェクトには適用されないことです。
配列を関数に渡し、その中で編集した場合、配列の内容は変わりますか?structとclassの挙動の違いを説明してください。
例を伴う答え:
func mutate(_ arr: inout [Int]) { arr.append(100) } var source = [1, 2] mutate(&source) print(source) // [1, 2, 100]
inoutを使用しない場合、関数内で最初に変更が行われた際に自動的にコピーが行われ、元の配列は変更されません。一方、classの場合はコピーは行われず、元のオブジェクトは常に変更されます。
物語
開発者たちは参照オブジェクトを構造体の配列に格納し、1つの構造体を介して変更しても他のインスタンスに影響しないことを期待していました。しかし実際には、ある場所で参照オブジェクトを変更すると、それがすぐにどこでも変更されることになりました(shared state)。
物語
チームプロジェクトでは、race conditionからの保護を図るため、各関数の呼び出しごとにコレクションをコピーしようとしました。この結果、メモリオーバーヘッドが予期せぬ形で増大し、大規模な配列を扱う際のパフォーマンスが低下しました。
物語
若い開発者は配列の変更を追跡しようとしたため、inoutを介して複数のハンドラー関数に同時に配列を渡しました。その結果、変更の順序が不明瞭になり、スレッドセーフでない変更、バグ、同期エラーを引き起こしました。