Swift: defer
と @escaping
の違いを理解する
非同期処理やリソースの解放を扱う際に、Swiftでは defer
と @escaping
クロージャの両方を利用することがあります。これらは同じような場面で使われることもありますが、その動作や用途は大きく異なります。本記事では、両者の違いを実際のコードサンプルを通して解説します。
1. defer
の動作を理解する
defer
は、スコープの終了時に必ず実行されるコードを登録するための機能です。ファイル操作やリソース解放など、クリーンアップ処理を簡潔に記述できます。
以下は、非同期処理で defer
を使用してファイルをクローズする例です。
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
クロージャを使用して非同期処理の完了を通知する例です。
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
を組み合わせて、非同期タスク内でリソースを解放しつつ完了通知を行う例です。
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
クロージャは非同期処理の終了後に外部へ通知する際に使います。 - 両者を適切に組み合わせることで、非同期処理を効率的かつ安全に実装することが可能です。
ぜひ実際のプロジェクトで試してみてください!