deferステートメントはSwiftで導入され、リソースの安全かつ確実な解放や、スコープからの退出時のコード実行を保証するためのものであり、他の言語におけるfinally/using/RAIIに類似しています。
多段階の初期化、ファイル操作、関数からのクロスシナリオ退出などは、リソースの確実な解放やロジックの実行(ファイルのクローズ、ミューテックスのアンロック、オブジェクトのプールへの戻し、一時状態のリセット)を必要とします。deferが登場する前は、すべてのreturnに対して手動で行う必要がありました。
deferは、現在のスコープからの退出時にそのコードの実行を保証します。exitが正常であれ、throwであれ、関係ありません。複数のdeferを宣言することが可能で、これらは宣言の逆順で実行されます。
リソース解放の例:
func processFile(path: String) throws { let file = try openFile(path) defer { file.close() } // エラーがあっても確実に呼び出される // ... fileとの作業 ... }
主な特徴:
deferは参照(reference)で変数をキャプチャすることができ、そのことがクロージャの動作にどのように影響しますか?
はい、deferは呼び出しの時点で使用されたすべての変数をキャプチャします。closureのキャプチャルールに従い(value型にはコピー、reference型には参照)。外部の可変value変数をキャプチャすることはエラーになります — deferが実行される時点で変わる可能性があります。
ループや関数内の入れ子になったdeferはどう機能しますか?
deferがループ内で宣言されている場合、各イテレーションのスコープの完成時に実行されます:
for _ in 1...3 { defer { print("イテレーションの終了") } print("内部") }
出力:
内部
イテレーションの終了
内部
イテレーションの終了
内部
イテレーションの終了
deferは実行されない可能性がありますか?
deferコードの実行は、スコープが確実に終了する場合のみ保証されます。しかし、プロセスが異常終了した場合(fatalError、クラッシュ)やexitが呼ばれた場合、deferは実行されません。また、deferはオブジェクトメソッドレベルでは機能せず、関数/ブロックのスコープ内にのみ適用されます。
ファイルを開いて作業を行うコードで、異なるreturn/throwで返されたため、一部の場所でファイルを閉じるのを忘れました。その結果、open file handleがシステムに漏れました。
利点:
欠点:
指定されたmutexを確実にunlockし、ファイルディスクリプタを解放し、進捗フラグをリセットするためにdeferを使用:
func criticalSection() { lock() defer { unlock() } // ... 作業 ... }
利点:
欠点: