defer は、現在のスコープを抜ける際にコードブロックの実行をスケジュールするSwiftの制御構文です。deferブロックは、スコープがどのように終了するかに関係なく実行されます — return、break、throw、fatalError、または通常の完了。Swift Language Guide(2025)によると、1つのスコープに複数のdeferが存在する場合、宣言の逆順で実行されます — 最後に宣言されたものが最初に実行されます(LIFO)。これにより、deferはリソースの確実なクリーンアップに不可欠なものとなっています:ファイル記述子のクローズ、ロックの解放、一時ポインタの解放を、早期終了時にクリーンアップを逃すリスクなく行えます。
重要ポイント
defer は、Swift 2.0(2015)で導入されたSwiftの制御構文で、現在のスコープが終了するまで自身のブロックの実行を延期します。主な特徴:deferは、スコープがどのように終了するかに関係なく、その本体が実行されることを保証します — 成功(return)、エラー(throw)、早期終了(break、continue)、または致命的終了(fatalError、precondition)。
構文的には、deferは defer { /* コード */ } のように記述され、スコープ内の任意の場所に配置できます。Swiftコンパイラは、deferの宣言とスコープ終了の間で例外やreturnが発生しても、defer内のコードが実行されることを保証します。これは、関数の最後に配置された通常のコードが早期終了時にスキップされる可能性があるのと根本的に異なります。
クリス・ラトナー(Swiftの生みの親、2015)の記事によると、deferは他の言語の類似構文 — Goのdefer、Java/Pythonのfinally、C++のscope guard — に触発されましたが、重要な違いがあります:Swiftでは、deferはスコープの最後で実行され、try-catchブロックの直後ではありません。これにより、複数の終了ポイントを持つ関数でのクリーンアップの動作がより予測可能になります。
対称的なリソース管理にdeferを使用してください:ファイルを開く → defer { close }、ロックを取得 → defer { unlock }。このパターンは、どのような状況でもリソースの解放が決して見逃されないことを保証します。
同じスコープで複数のdeferが宣言されている場合、それらは宣言の逆順で実行されます(LIFO — Last In, First Out)。つまり、最後に宣言されたdeferが最初に実行され、最初に宣言されたものが最後に実行されます:
func exampleDeferOrder() {
defer { print("1番目のdefer") }
defer { print("2番目のdefer") }
defer { print("3番目のdefer") }
print("関数本体")
}
// 出力:
// 関数本体
// 3番目のdefer
// 2番目のdefer
// 1番目のdefer
LIFO順序は、入れ子になったリソースの正しい管理にとって重要です。ファイルAが最初に開かれ、次にファイルBが開かれた場合、逆順に解放する必要があります:最初にB、次にA。deferを使用すると、これは自動的に行われます — 各リソースを開いた直後にdeferを宣言すれば、関数の終了ポイントの数に関係なくクリーンアップ順序は正しくなります。
Swift by Sundell(2024)によると、この機能により、deferは入れ子になったロックやトランザクションに最適です:ロックを取得 → defer { unlock } → 次を取得 → defer { unlock }。LIFOは、ロックが取得と逆順で解放されることを保証し、デッドロックを防ぎます。
deferの主な用途はリソースの確実なクリーンアップです。ファイルシステムでの作業を考えてみましょう。FileHandleを介してファイルを開くには明示的なクローズが必要です — deferは、どのようなシナリオでもcloseが呼び出されることを保証します:
func readFile(path: String) throws -> String {
let handle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path))
defer { try? handle.close() }
let data = try handle.readToEnd()
guard let data else { throw FileError.empty() }
return String(data: data, encoding: .utf8) ?? ""
// throwまたはreturnでもhandle.close()が呼び出されます
}
もう1つの典型的なシナリオは、ローディングフラグを使用したUIアニメーションです。ロードを開始する前に、フラグisLoading = trueを設定し、deferはリクエストの成功や失敗に関係なく、関数終了時にフラグをfalseに戻します。これにより、未処理のエラーによってフラグがtrueのままになり、インターフェースが永久にブロックされるのを防ぎます。
Bitbucket Engineering Blog(2024)によると、deferはプロファイリングにも使用されます:関数の開始時に時間を記録し、deferで差分を計算して出力します。これにより、エラー経路を含むすべての実行経路の正確なパフォーマンス測定が得られます。
defer は throws 関数と効果的に連携します。関数が任意の段階でエラーをスローする可能性がある場合、deferはすべてのcatchブロックやguardによる早期終了でコードを重複させることなくクリーンアップを保証します:
func processTransaction() throws {
let db = try openDatabase()
defer { closeDatabase(db) }
let user = try fetchUser(from: db)
defer { logAudit(user) }
let result = try performPayment(user)
sendNotification(result)
// closeDatabase(db)とlogAudit(user)が呼び出されます
// 任意のthrowまたはreturnで
}
重要:deferは、制御がcatchブロックの外に移される前に実行されますが、エラーが発生した後です。defer内でエラーがスローされた場合、Swiftはdefer内で直接 try を使用することを許可しません — try? または try! が必要です。Appleのドキュメントによると、Swiftはdeferからのエラーのエスケープを許可しません。これはブロックの実行保証に違反するためです。
リソース取得直後にdeferを配置してください。これは近接性の原則に従います:読み手は取得と解放を一緒に見ることができ、コードの信頼性が向上し、コードレビューが簡素化されます。
defer は、宣言されたスコープを抜ける際に実行されます。deferが do ブロック内で宣言された場合、そのブロックを抜ける際に実行され、外側の関数ではありません。for ループ内にある場合 — 各反復で実行されます:
func scopeExample() {
print("start")
do {
defer { print("doブロックdefer") }
print("inside do")
}
// "doブロックdefer"はここに出力
print("after do")
}
// 出力: start, inside do, doブロックdefer, after do
for i in 1...3 {
defer { print("end iteration \(i)") }
print("iteration \(i)")
}
// 出力: iteration 1, end iteration 1, iteration 2, end iteration 2, ...
deferによってキャプチャされた変数は、deferの宣言時ではなく、スコープ終了時に読み込まれます。deferの宣言からスコープ終了までの間で変数が変更された場合、deferは最新の値を参照します。これは、キャプチャが作成時に固定されるクロージャとの重要な違いです。注意してください:deferを宣言した後の変数の変更は、その実行に影響を与えます。
最初の間違い — LIFO以外の実行順序を想定すること。クリーンアップの順序が重要で、deferが間違った順序で宣言されている場合、依存関係に違反してリソースが解放される可能性があります。解決策:各リソースをキャプチャした直後にdeferを宣言してください。2番目のリソースが開かれた → defer { close second } を最初のリソースが閉じられる前に。
2番目の間違い — クリーンアップに関係のないロジックにdeferを使用すること。deferは確実なクリーンアップのためのものであり、メインのフロー制御のためではありません。defer内のコードが戻り値に影響を与える場合、それはほぼ常に間違いです。deferは関数の戻り値を変更できません(Javaのfinallyとは異なり、finally内のreturnは元のreturnを上書きします)。
3番目の間違い — deferからエラーをスローすること。Swiftは、エラーが外部に伝播する可能性がある場合、defer内での try を禁止しています。スローする可能性のある操作には try? または try! を使用するか、throwsなしの別の関数にラップしてください。O’Reilly “Swift in Depth”(2025)によると、良いプラクティスは、クリーンアップ関数をnon-throwingにするか、defer内でエラーを処理することです。
よくある質問
defer は、現在のスコープが終了するまでブロックの実行を延期するSwiftの構文です。ブロックは常に実行されます — return、throw、break、または通常の完了時。リソースの確実なクリーンアップ(ファイルのクローズ、ロックの解放)に使用されます。
宣言の逆順(LIFO) — 最後に宣言されたdeferが最初に実行されます。これにより、入れ子になったリソースの正しいクリーンアップが保証されます:リソースBがAの後に開かれた場合、Aの前に閉じられ、既に解放されたリソースへの依存を防ぎます。
直接はできません — Swiftはdeferからのエラー伝播を防ぎます。スローする可能性のある操作には try? または try! を使用してください。ベストプラクティスは、クリーンアップ関数をnon-throwingにするか、defer内でエラーを外部に伝播させずに処理することです。
defer はスコープに結びついており、return、throw、breakを含む任意の終了時に実行されます。finally(他の言語)はtry-catchに結びついており、tryが存在する場合にのみ実行されます。Swiftにはfinallyはありません — deferはこのシナリオを完全にカバーし、エラー処理だけでなく任意のスコープで機能します。
はい、deferは宣言時ではなく、スコープ終了時に変数を読み取ります。deferが宣言された後に変数が変更された場合、deferブロックは最新の値を参照します。これは、キャプチャが作成時に固定される通常のクロージャとは異なります。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。