MENU

【Swift入門】@escapingとは?使い方を初心者向けに徹底解説

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が必要かどうかは以下の質問で判断できます。

  1. クロージャは関数が終了する前に必ず実行されますか?
    • はい → @escaping不要
    • いいえ → @escaping必要
  2. クロージャをプロパティに保存しますか?
    • はい → @escaping必要
    • いいえ → 質問1へ
  3. クロージャを別の@escapingクロージャ内で使いますか?
    • はい → @escaping必要
    • いいえ → 質問1へ

まとめ

@escapingは、クロージャが関数のスコープを超えて「脱出」する可能性があることを示すキーワードです。

主な使用ケースは以下の通りです。

  • 非同期処理(ネットワーク通信、タイマーなど)
  • クロージャをプロパティに保存する場合
  • 別のescapingクロージャ内で使用する場合

@escapingクロージャを使う際は、循環参照に注意し、[weak self][unowned self]を適切に使用しましょう。

最初は難しく感じるかもしれませんが、実際のコードで使っていくうちに自然と理解できるようになります。非同期処理を扱う際は、まず@escapingを思い出してみてください。

プログラミングの独学におすすめ
プログラミング言語の人気オンラインコース
独学でプログラミングを学習している方で、エラーなどが発生して効率よく勉強ができないと悩む方は多いはず。Udemyは、プロの講師が動画で実際のプログラムを動かしながら教えてくれるオンライン講座です。講座の価格は、セール期間中には専門書籍を1冊買うよりも安く済むことが多いです。新しく学びたいプログラミング言語がある方は、ぜひUdemyでオンライン講座を探してみてください。
目次