Swift で処理を書いていると、
「この処理は、成功しても失敗しても、最後に必ず実行したい」
という場面が出てきます。
こうした、最後に必ず実行したい処理を
コード上で設計として表現できるのが、
Swift の defer です。
この記事では、この defer がどのような構文なのかを解説していきます。
defer とは?
defer は、
その defer が書かれているスコープを抜ける直前に実行される処理を定義するための構文です。
以下、defer の書き方です。
defer {
// この中に、スコープの終了時に必ず実行したい処理を記述する
}
実際にいつ実行されるかは、
この defer が書かれているスコープによって決まります。
defer の処理されるタイミング
defer の中の処理は、
その defer を含むスコープの最後で実行されます。
ここで重要なのは、
「関数の最後」とは限らない、という点です。
defer は、
書かれた場所のスコープに紐づいて動作します。
if 文との兼ね合い
公式ドキュメントでは、
if 文と defer の関係を示す例が紹介されています。
func f(x: Int) {
defer { print("First defer") }
if x < 10 {
defer { print("Second defer") }
print("End of if")
}
print("End of function")
}
f(x: 5)
このコードを実行すると、出力は次の順になります。
End of if
Second defer
End of function
First defer
この結果から分かるのは、
defer は「関数の最後にまとめて実行される」わけではない、ということです。
それぞれの defer は、
自分が属しているスコープが終了するタイミングで実行されています。
この例では、
if文のスコープが先に終了する- 関数全体のスコープはその後に終了する
という流れになるため、
if 文内の defer が先に実行され、
関数スコープの defer は最後に実行されます。
実際の使用例(朝の実)
私が現在開発中の朝専用SNSアプリ「朝の実」では、
投稿処理の実装において defer を使用しています。
投稿処理では、
- 投稿中であることを示すフラグを立てる
- そのフラグは成功・失敗に関わらず、最後に必ず元に戻す
という要件があります。
@MainActor
final class PostViewModel: ObservableObject {
@Published var text: String = ""
@Published var isPosting: Bool = false
@Published var errorMessage: String?
func post() async {
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else {
errorMessage = "本文を入力してください"
return
}
// 処理開始時にフラグを立てる
isPosting = true
// スコープ終了時に必ずフラグを戻す
defer {
isPosting = false
}
do {
try await saveToCloudKit(text: trimmed)
text = ""
} catch {
errorMessage = "投稿に失敗しました"
}
}
private func saveToCloudKit(text: String) async throws {
// CloudKit への保存処理(省略)
}
}
defer を使うことで、
「必ずフラグを戻す」という意図を、
分岐に関係なく 1 か所にまとめて表現できます。
まとめ
defer は、単なる便利構文ではなく、
「最後に必ず実行したい処理を設計として切り出す」という意図を明確にできます。
スコープと実行タイミングを正しく理解することで、
非同期処理やエラー処理を含むコードでも、
意図の伝わる、安全な実装につながると考えています。
