Swiftは、inoutパラメータの渡し方とisUniquelyReferencedランタイム関数の組み合わせを通じてインプレースの変更を可能にしています。mutatingメソッドが呼び出されると、コンパイラはSILレベルで呼び出しをinoutパラメータに変換し、呼び出しの間、そのメソッドに対して値のメモリへの独占アクセスを付与します。ヒープに割り当てられたバックストレージをクラス参照を介して共有する前に、ランタイムは参照カウントが正確に1であるかどうかをisUniquelyReferencedを使用して確認します。真であれば、直接変更を行います。そうでない場合、保護的コピーを作成します。独占性の法則は、コンパイル時の静的解析と動的ランタイム計測を通じて強制されており、重要なチェック-変更ウィンドウの間、他のスレッドや実行パスが値にアクセスできないことを保証し、競合状態を防止しながら冗長な割り当てなしに値セマンティクスを維持します。
高性能な写真編集アプリケーションを開発し、50メガピクセルのバイト配列をラップするカスタムImageBuffer構造体を使用して生のRAW画像データを処理すると想像してください。各フィルタ適用(ぼかし、シャープ、または色補正)は数百万のピクセルを修正する必要があり、ユーザーは10回以上の調整を連続して行う際にリアルタイムプレビューを期待します。数秒の遅延やメモリクラックなしで。
1つの潜在的な解決策は、共有可変状態を介してコピーオーバーヘッドを排除するために、ImageBufferをstructからclassに変換することを含みました。このアプローチはフィルターチェイン中の物理メモリの重複を防ぎましたが、バックグラウンドレンダリングスレッドがバッファに同時にアクセスしたときに重度のスレッドセーフティーハザードを引き起こし、元の画像データが取り消し履歴スタックを介して共有されることで、値セマンティクスが崩れる結果となりました。
別の考えられたアプローチは、すべてのフィルタ操作の前にピクセルバッファ全体を手動で深くコピーして、段階間の完全な隔離を確保することでした。この戦略は完璧な値セマンティクスとスレッドセーフティを維持しましたが、パフォーマンスが壊滅的に低下しました。1つの高解像度画像を12個のフィルタで処理するためには、数百メガバイトのメモリを12回コピーする必要があり、多秒の遅延と200MBを超えるデバイスの物理制限を超えるメモリスパイクが発生しました。
選択された解決策は、ImageBuffer structによって参照されるプライベートバックアップStorageクラス(final Swiftクラス)を使用してCopy-on-Writeセマンティクスを実装しました。各ミューテーティングフィルタメソッドは、まずストレージインスタンスでisUniquelyReferencedを呼び出しました。逐次処理中、最初の変化がコピーを引き起こし、その後の同じバッファインスタンスに対する変化は、割り当てなしでインプレースで操作されました。この設計により、Swiftの値セマンティクスが維持され、安全な元に戻す/やり直す操作が効率的なstructコピーを通じて可能になり、フィルターチェイン中の冗長なメモリの重複を回避することでインタラクティブなパフォーマンスが保たれました。
その結果、ユーザーは高解像度画像に十二の連続フィルタを適用し、100ミリ秒未満の応答時間で安定したメモリ使用において200MB未満を維持でき、過去の数ギガバイトのピークと過剰なコピーによって引き起こされたアプリケーションフリーズと比較して流れるような編集体験を得ることができました。
Objective-Cオブジェクトに対してisUniquelyReferencedがfalseを返すのはなぜですか? たった1つのSwift変数が参照を保持しているように見えるときでも。
Objective-Cオブジェクトには、関連オブジェクトからの保持されていない参照、NSNotificationCenter登録、またはKVOオブザーバーなど、Swiftの参照カウントメカニズムには見えない「追加の」参照が含まれている可能性があります。isUniquelyReferenced関数は、強い参照カウントが1であり、オブジェクトが「純粋なSwift」ネイティブオブジェクトであるかどうかを特に確認します。NSObjectサブクラスに対して、Objective-Cランタイムはオブジェクトを保持することがありますが、Swiftが観察できる方法でカウントを更新しない場合もあります。また、オブジェクトは不滅(シングルトン)である可能性があります。そのため、Swiftはこの制限を明示的に処理するためにisUniquelyReferencedNonObjCを提供しますが、開発者は一般的にCOWバックストレージが純粋なSwiftクラスであることを確認して、正確なユニーク性検出を保証し、不要にコピーが発生してパフォーマンスが低下するのを避けるべきです。
独占性の法則は、同時コンテキストでユニーク性チェック中に競合状態をどのように防ぐのですか?
独占性の法則は、可変値へのアクセスはそのアクセスの間、独占的でなければならないと規定しており、静的コンパイル時分析と動的ランタイムトラッキングの組み合わせによって強制されます。mutatingメソッドがisUniquelyReferencedチェックを実行する際、ランタイムはそのメモリ位置に対してすでに独占アクセスレコードを確立しています。このウィンドウの間に別のスレッドが値を読み取ったり書き込んだりしようとすると、独占違反が直ちに検出されます—静的違反の場合はコンパイル時、動的違反の場合はランタイムトラップによって。このことは、あるスレッドがユニーク性検証と実際の変異の間に参照カウントをインクリメントする可能性のある「チェック後アクション」競合状態を防ぎます。そうでなければ、2つのスレッドが同時に共有バッファを変更することになり、値セマンティクスが侵害され、データ破損やクラッシュを引き起こすことになります。
COWバックストレージはなぜ構造体ではなくクラスとして実装する必要があり、構造体が使用された場合にはどのような失敗モードが発生しますか?
Copy-on-Writeは、防御的コピーが必要な時を追跡するために共有可変状態を必要とします。共有参照カウントは値型ラッパーのすべてのコピーにわたって提供されるのは参照型(クラス)のみです。開発者が誤ってバックストレージをstructとして実装すると、親値型の各代入はストレージラッパーの異なるコピーを作成し、参照カウントフィールド自体が共有されるのではなく複製されます。その結果、isUniquelyReferencedは各コピーに対して常にtrueを返し、実装は独占を誤って仮定し、論理的に共有されたバッファにインプレース変異を行うことになります。これにより、1つのstructインスタンスを変更すると、別の明らかに独立した変数を通じて可視のデータが予期せず変更されるという交差値の変異バグが発生します。