Swiftでクロージャを扱う際に出てくる@escaping
キーワード。エラーメッセージで見かけたことがあっても、その意味や使い方がよく分からない方も多いのではないでしょうか。
この記事では、Swift初心者の方でも理解できるように、@escaping
の基本から実践的な使い方まで、分かりやすく解説していきます。
@escapingとは?基本を理解しよう
@escaping
は、クロージャが関数の実行完了後も「生き残る」可能性があることを示すキーワードです。
通常のクロージャとの違い
まず、通常のクロージャの動きを見てみましょう。
// 通常のクロージャ(@escapingなし)
func simpleFunction(action: () -> Void) {
print("処理開始")
action() // ここですぐに実行
print("処理終了")
}
simpleFunction {
print("クロージャ実行")
}
// 出力:
// 処理開始
// クロージャ実行
// 処理終了
このコードでは、action
クロージャはsimpleFunction
内ですぐに実行され、関数が終了すると同時に破棄されます。
@escapingが必要なケース
一方、クロージャが関数の実行後に呼び出される場合は@escaping
が必要です。
// @escapingを使ったクロージャ
func delayedFunction(completion: @escaping () -> Void) {
print("処理開始")
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
completion() // 2秒後に実行(関数はすでに終了している)
}
print("処理終了")
}
delayedFunction {
print("クロージャ実行")
}
// 出力:
// 処理開始
// 処理終了
// (2秒後)
// クロージャ実行
delayedFunction
は即座に終了しますが、completion
クロージャは2秒後に実行されます。関数のスコープから「脱出(escape)」するため、@escaping
が必要になります。
@escapingが必要な3つの主要パターン
パターン1: 非同期処理
最も一般的な使用例は、ネットワーク通信などの非同期処理です。
class APIClient {
func fetchUserData(completion: @escaping (User?, Error?) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
// ネットワーク処理完了後にクロージャを実行
if let error = error {
completion(nil, error)
return
}
// データをパース
let user = parseUser(from: data)
completion(user, nil)
}.resume()
}
}
// 使用例
let client = APIClient()
client.fetchUserData { user, error in
if let user = user {
print("ユーザー名: \(user.name)")
}
}
パターン2: プロパティへの保存
クロージャをクラスのプロパティに保存する場合も@escaping
が必要です。
class Button {
var actionHandler: (() -> Void)?
func setAction(_ handler: @escaping () -> Void) {
self.actionHandler = handler // プロパティに保存
}
func tap() {
actionHandler?() // 後で実行される
}
}
// 使用例
let button = Button()
button.setAction {
print("ボタンがタップされました")
}
button.tap() // ボタンがタップされました
パターン3: 別のescapingクロージャ内での使用
既に@escaping
指定されたクロージャの中で使う場合も必要です。
class TaskManager {
func performTask(
preparation: @escaping () -> Void,
execution: @escaping () -> Void
) {
DispatchQueue.global().async {
preparation() // preparationもescapingクロージャ内で使用
DispatchQueue.main.async {
execution()
}
}
}
}
@escapingを使わない場合のメリット
デフォルトでは、クロージャは@escaping
ではありません(non-escaping)。これには理由があります。
メモリ管理が簡単
func processData(action: () -> Void) {
action()
} // actionはここで自動的に解放される
non-escapingクロージャは関数のスコープ内でのみ有効なため、循環参照のリスクが低く、メモリ管理が簡単です。
パフォーマンスの最適化
コンパイラは、non-escapingクロージャに対してより積極的な最適化を行えます。
@escapingと循環参照の注意点
@escaping
クロージャを使う際の最大の注意点は、循環参照(retain cycle)です。
問題のあるコード
class ViewController {
var name = "ホーム画面"
func loadData() {
APIClient().fetchData { data in
// selfを強参照してしまう
print("\(self.name)のデータを読み込みました")
}
}
}
正しい書き方
class ViewController {
var name = "ホーム画面"
func loadData() {
APIClient().fetchData { [weak self] data in
guard let self = self else { return }
print("\(self.name)のデータを読み込みました")
}
}
}
[weak self]
または[unowned self]
を使って、弱参照にすることで循環参照を防ぎます。
実践的な使用例:ダウンロードマネージャー
実際のアプリ開発でよく使われるパターンを見てみましょう。
class DownloadManager {
private var progressHandlers: [String: (Double) -> Void] = [:]
private var completionHandlers: [String: (URL?, Error?) -> Void] = [:]
func download(
url: URL,
progress: @escaping (Double) -> Void,
completion: @escaping (URL?, Error?) -> Void
) {
let taskID = UUID().uuidString
// クロージャを保存
progressHandlers[taskID] = progress
completionHandlers[taskID] = completion
// ダウンロード処理を開始
let task = URLSession.shared.downloadTask(with: url) { [weak self] location, response, error in
// 進捗を通知
progress(1.0)
// 完了を通知
completion(location, error)
// クリーンアップ
self?.progressHandlers.removeValue(forKey: taskID)
self?.completionHandlers.removeValue(forKey: taskID)
}
task.resume()
}
}
// 使用例
let manager = DownloadManager()
manager.download(
url: imageURL,
progress: { progress in
print("進捗: \(Int(progress * 100))%")
},
completion: { url, error in
if let url = url {
print("ダウンロード完了: \(url)")
}
}
)
@escapingを使うべきか判断する方法
クロージャを引数に取る関数を作る際、@escaping
が必要かどうかは以下の質問で判断できます。
- クロージャは関数が終了する前に必ず実行されますか?
- はい →
@escaping
不要 - いいえ →
@escaping
必要
- はい →
- クロージャをプロパティに保存しますか?
- はい →
@escaping
必要 - いいえ → 質問1へ
- はい →
- クロージャを別の
@escaping
クロージャ内で使いますか?- はい →
@escaping
必要 - いいえ → 質問1へ
- はい →
まとめ
@escaping
は、クロージャが関数のスコープを超えて「脱出」する可能性があることを示すキーワードです。
主な使用ケースは以下の通りです。
- 非同期処理(ネットワーク通信、タイマーなど)
- クロージャをプロパティに保存する場合
- 別のescapingクロージャ内で使用する場合
@escaping
クロージャを使う際は、循環参照に注意し、[weak self]
や[unowned self]
を適切に使用しましょう。
最初は難しく感じるかもしれませんが、実際のコードで使っていくうちに自然と理解できるようになります。非同期処理を扱う際は、まず@escaping
を思い出してみてください。