Swift: defer と @escaping の違いを理解する

# Swift: `defer` と `@escaping` の違いを理解する

非同期処理やリソースの解放を扱う際に、Swiftでは `defer` と `@escaping` クロージャの両方を利用することがあります。これらは同じような場面で使われることもありますが、その動作や用途は大きく異なります。本記事では、両者の違いを実際のコードサンプルを通して解説します。

---

## 1. `defer` の動作を理解する

`defer` は、スコープの終了時に必ず実行されるコードを登録するための機能です。ファイル操作やリソース解放など、クリーンアップ処理を簡潔に記述できます。

以下は、非同期処理で `defer` を使用してファイルをクローズする例です。

```swift
import Foundation

func asyncFileOperationWithDefer() {
    let fileURL = URL(fileURLWithPath: "/path/to/file.txt")

    DispatchQueue.global().async {
        print("Start of async task")

        guard let fileHandle = try? FileHandle(forWritingTo: fileURL) else {
            print("FileHandle failed")
            return
        }

        defer {
            print("FileHandle closed")
            fileHandle.closeFile()
        }

        print("Writing to file...")
        fileHandle.write("Hello, Swift!".data(using: .utf8)!)

        print("End of async task")
    }
}

asyncFileOperationWithDefer()
```

### 実行結果（想定）

```
Start of async task
Writing to file...
End of async task
FileHandle closed
```

- `defer` は非同期タスク内のスコープ終了時に実行されます。
- この例では、ファイルのクローズ処理が `defer` によって保証されています。

## 2. `@escaping` クロージャの動作を理解する

一方で、`@escaping` クロージャは、非同期処理が完了したタイミングで任意の処理を実行する際に使います。特定のタスクが終了した後に通知を受けたり、別の処理を開始したりするのに便利です。

以下は、`@escaping` クロージャを使用して非同期処理の完了を通知する例です。

```swift
import Foundation

func asyncFileOperationWithEscaping(completion: @escaping (Result<Void, Error>) -> Void) {
    let fileURL = URL(fileURLWithPath: "/path/to/file.txt")

    DispatchQueue.global().async {
        print("Start of async task")

        guard let fileHandle = try? FileHandle(forWritingTo: fileURL) else {
            print("FileHandle failed")
            completion(.failure(NSError(domain: "FileError", code: 1, userInfo: nil)))
            return
        }

        print("Writing to file...")
        fileHandle.write("Hello, Swift!".data(using: .utf8)!)
        fileHandle.closeFile()

        print("End of async task")
        completion(.success(()))
    }
}

asyncFileOperationWithEscaping { result in
    switch result {
    case .success:
        print("File operation completed successfully.")
    case .failure(let error):
        print("File operation failed with error: \(error)")
    }
}
```

### 実行結果（想定）

```
Start of async task
Writing to file...
End of async task
File operation completed successfully.
```

- 非同期処理が完了したタイミングで `completion` クロージャが呼び出されます。
- ファイルのクローズ処理も含めて、全体の成功/失敗を通知することができます。

---

## 3. `defer` と `@escaping` の違い

| 特性                        | `defer`                                      | `@escaping`                                |
|-----------------------------|----------------------------------------------|--------------------------------------------|
| **実行タイミング**           | スコープ終了時                              | 非同期処理の完了時                          |
| **用途**                   | クリーンアップ処理（リソース解放など）       | 非同期処理の結果通知や次の処理のトリガー    |
| **非同期処理の外で利用可能か** | ×                                          | ○                                          |
| **複数の呼び出し**           | 一度のみ（スコープ終了時）                  | 任意のタイミングで複数回呼び出し可能        |

---

## 4. どちらを選ぶべきか？

- **`defer` を使うべき場面：**
  - 非同期処理の内部で、リソースの解放やエラーハンドリングをスコープ内に簡潔にまとめたい場合。

- **`@escaping` を使うべき場面：**
  - 非同期処理の終了後に外部のコンポーネントに通知したい場合や、完了後の処理を柔軟に扱いたい場合。

---

## 5. 両者を組み合わせる

以下は、`defer` と `@escaping` を組み合わせて、非同期タスク内でリソースを解放しつつ完了通知を行う例です。

```swift
import Foundation

func asyncFileOperationCombined(completion: @escaping (Result<Void, Error>) -> Void) {
    let fileURL = URL(fileURLWithPath: "/path/to/file.txt")

    DispatchQueue.global().async {
        print("Start of async task")

        guard let fileHandle = try? FileHandle(forWritingTo: fileURL) else {
            print("FileHandle failed")
            completion(.failure(NSError(domain: "FileError", code: 1, userInfo: nil)))
            return
        }

        defer {
            print("FileHandle closed")
            fileHandle.closeFile()
        }

        print("Writing to file...")
        fileHandle.write("Hello, Swift!".data(using: .utf8)!)

        print("End of async task")
        completion(.success(()))
    }
}

asyncFileOperationCombined { result in
    switch result {
    case .success:
        print("File operation completed successfully.")
    case .failure(let error):
        print("File operation failed with error: \(error)")
    }
}
```

この実装では、`defer` でリソースを解放しつつ、非同期タスクの完了を `@escaping` クロージャで通知しています。

---

## まとめ

- `defer` はスコープ内でのリソース管理やクリーンアップ処理に最適です。
- `@escaping` クロージャは非同期処理の終了後に外部へ通知する際に使います。
- 両者を適切に組み合わせることで、非同期処理を効率的かつ安全に実装することが可能です。

ぜひ実際のプロジェクトで試してみてください！