ProgrammingiOS開発者

Swiftにおけるdeferの動作メカニズムについて説明し、注意すべき境界条件や特徴は何ですか?

Hintsage AIアシスタントで面接を突破

回答。

deferは、Swiftの特別な構文で、関数のスコープを退出する直前に特定のコードブロックを実行できるようにします。これは通常のreturnまたはエラー(throw)でスコープから退出した場合に関係なく行われます。deferは、リソースの解放、変更の取り消し、または操作の最終化に便利です。

動作の特徴:

  • 関数に複数のdeferがある場合、それらは逆順で実行されます(LIFO:最後に入ったものが最初に出る)。
  • deferはエラー(throw)が発生した場合でも、または関数からのいかなるreturnでも実行されます。
  • closure内のdeferは、closureを呼び出す場所ではなく、closureの本体に関連付けられます。

例:

func testDefer() { print("begin") defer { print("first defer") } defer { print("second defer") } print("end") } // 出力: // begin // end // second defer // first defer

境界条件:

  • deferは、deferが出現した時点での変数の値をキャプチャします。defer closure内で変数を使用する場合、deferの実行時にその変更が反映されます。
  • 関数がfatalErrorで終了した場合、deferは呼び出されません。

ひねりのある質問。

fatalErrorを呼び出した場合、関数内のすべてのdeferブロックは実行されますか?

回答: いいえ、関数内でfatalError(またはそれに類似した制御不能なクラッシュ)が呼び出されると、すべてのdeferで遅延させたブロックは実行されません。deferはアプリケーションの異常終了時にコードを呼び出すことを保証しません。

例:

func foo() { defer { print("Defer 1") } fatalError("Oops") defer { print("Defer 2") } } foo() // 何も出力されず、クラッシュします

このトピックの詳細を知らないことによる実際のエラーの例。


事例

プロジェクトでファイルディスクリプタを閉じるためにdeferを使用しました。エラーが発生した際に適切なthrowの代わりにfatalErrorを使用し、クラッシュ時にdeferが動作しないためにオープンリソースがリークしました。


事例

ある関数に複数のdeferがあり、そのうちいくつかはローカル変数の状態に依存していました。開発者は変数が特定の値であると期待していましたが、その値がdeferの中で他のコードの部分で変更され、deferの実行時に最新の値が使用され、間違ったIDによるトランザクション取り消しのバグを引き起こしました。


事例

ネストされたclosure内でdeferブロックを書き、これが外部関数から出るときに実行されると考えました。結果として、このdeferはclosureから出るときに実行され、関数全体から出るときではなく、リソースが早すぎて解放されてしまいました。