Swiftで理解する@escapingの9選サンプルコード

Swiftの@escaping属性の詳細解説とサンプルコードSwift
この記事は約19分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Swiftの開発において、クロージャの概念は中心的な役割を果たします。

特に、@escaping属性はSwiftの関数型プログラミングの柔軟性と表現力を高める重要な要素です。

この記事では、Swiftの@escaping属性に焦点を当て、その基本から応用までを深く掘り下げていきます。

初心者から上級者まで、この記事を通じて@escapingの使い方や注意点、カスタマイズ方法まで理解を深めることができるでしょう。

●Swiftの@escaping属性とは

Swiftでクロージャを使用する際、特に重要なのが@escaping属性です。

クロージャとは、簡単に言えば関数と同様にコードのブロックを変数や引数として渡すことができる機能のことです。

@escaping属性は、このクロージャが関数の実行が完了した後も使用される可能性がある場合に使います。

この属性の主な目的は、メモリ管理と実行の流れの制御です。

Swiftのメモリ管理システムは、@escaping属性がついていないクロージャに対しては、特定の最適化を行います。

そのため、クロージャが関数のスコープ外で実行される可能性がある場合は、@escaping属性を明示的に指定する必要があります。

○@escaping属性の基本

@escaping属性は、クロージャが関数の引数として渡されるが、関数の実行が終わった後もクロージャが生き続ける可能性がある場合に使用します。

具体的には、非同期処理、ネットワークリクエストの完了ハンドラ、またはクロージャを含むオブジェクトが長生きする場合などに必要となります。

Swiftでは、デフォルトでは全てのクロージャはnon-escapingとして扱われます。

つまり、関数がクロージャを引数として受け取った際、その関数の実行が終わると同時にクロージャも破棄されます。

@escaping属性を使うことで、このデフォルトの挙動を変更し、クロージャが関数のスコープ外でも生存できるようになります。

○@escaping属性が必要なシチュエーション

□非同期処理

最も一般的な@escapingクロージャの使用例は、非同期処理です。

たとえば、データのダウンロードやデータベースへの問い合わせなどの操作を行う際、これらの処理はすぐには完了しません。

そのため、これらの操作が完了した後に実行する処理をクロージャとして渡す必要があり、そのクロージャは@escapingである必要があります。

□遅延実行

@escaping属性は、関数やメソッドから返された後に実行されるクロージャにも使用されます。

例えば、ある条件が満たされた時にのみ実行されるような遅延実行の処理において、クロージャは関数の生存期間を超えて存在する必要があるため、@escaping属性が必要になります。

□クロージャを含むオブジェクトの保持

あるオブジェクトがクロージャをプロパティとして保持している場合も、@escaping属性が必要です。

例えば、ユーザーのアクションに基づいて何らかの処理を行うコールバックとしてクロージャを保持するUIコンポーネントなどがこれに該当します。

●@escaping属性の使い方

Swiftにおいて、クロージャや関数がどのように動作するのかを細かく制御するためには、さまざまな属性や修飾子を使用することが必要です。

その中でも、特に重要な役割を果たすのが@escaping属性です。

ここでは、Swiftでの@escaping属性の正確な使い方について、サンプルコードを交えて解説します。

○サンプルコード1:基本的な@escapingの使い方

このコードでは、関数内で非同期に実行されるクロージャを受け取る例を表しています。

この例では、クロージャが関数のスコープ外で呼び出されることを表しています。

func asyncFunction(completion: @escaping () -> Void) {
    DispatchQueue.global().async {
        // 非同期に何かの処理を行う
        completion()
    }
}

asyncFunction {
    print("非同期処理が完了しました。")
}

上記のコードでは、asyncFunctionという関数が@escaping属性を持つクロージャを引数として受け取ります。

関数内で、非同期にクロージャが実行されるため、@escaping属性が必要となります。

このコードを実行すると、”非同期処理が完了しました。”というメッセージが非同期に出力されます。

クロージャが関数の実行が完了した後に実行されるため、@escaping属性が必要となるのです。

○サンプルコード2:非逃避クロージャの例

次に、@escaping属性を持たない、いわゆる非逃避クロージャの例について見ていきましょう。

このコードでは、クロージャが関数内で完結し、関数のスコープ外で呼び出されることがない場面を表しています。

func nonEscapingFunction(closure: () -> Void) {
    closure()
}

nonEscapingFunction {
    print("関数内でクロージャが完結しています。")
}

このコードでは、nonEscapingFunctionという関数がクロージャを引数として受け取り、そのまま関数内で実行しています。

このように、クロージャが関数のスコープ内で完結する場合、@escaping属性は不要です。

このコードを実行すると、”関数内でクロージャが完結しています。”というメッセージが出力されます。

クロージャは関数内で直接実行されるため、非逃避クロージャとして扱われます。

○サンプルコード3:逃避クロージャの例

@escaping属性は、クロージャが関数のスコープを「逃避」する場合に必要となります。

逃避クロージャとは、関数の実行が終了した後も生存し続けるクロージャのことを指します。

一般的に、非同期処理や変数としてクロージャを保持する際などに使用されます。

このコードでは、逃避クロージャを使って非同期処理の終了後にクロージャが実行される例を表しています。

この例では、DispatchQueueを使って非同期に処理を実行し、その後に指定したクロージャを呼び出しています。

import Foundation

// クロージャを引数として受け取る関数
func executeAfterDelay(delay: TimeInterval, completion: @escaping () -> Void) {
    DispatchQueue.global().asyncAfter(deadline: .now() + delay) {
        completion()
    }
}

// 使用例
executeAfterDelay(delay: 2.0) {
    print("2秒後にこのメッセージが表示されます。")
}

このサンプルコードを実行すると、2秒後にコンソールに「2秒後にこのメッセージが表示されます。」というメッセージが表示されることが期待されます。

このように、@escapingを使用することで関数の実行が終了した後でもクロージャを呼び出すことができるのです。

○サンプルコード4:クロージャを関数の引数として使用する例

@escaping属性は、クロージャを関数の引数として使用する際にもしばしば遭遇します。

この場合、関数内でクロージャを直接呼び出すのではなく、何らかの形で保持し、後から呼び出す場面での使用が想定されます。

このコードでは、クロージャを引数として受け取り、内部の配列に保持する例を表しています。

この例では、TaskManagerというクラスを使って複数のタスクを管理しています。

class TaskManager {
    var tasks: [() -> Void] = []

    func add(task: @escaping () -> Void) {
        tasks.append(task)
    }

    func executeAll() {
        for task in tasks {
            task()
        }
    }
}

let manager = TaskManager()
manager.add(task: {
    print("タスク1を実行しました。")
})

manager.add(task: {
    print("タスク2を実行しました。")
})

manager.executeAll()

上記のサンプルコードを実行すると、コンソールに「タスク1を実行しました。」および「タスク2を実行しました。」というメッセージが表示されます。

このように、@escaping属性を持つクロージャは関数やメソッドのスコープ外でも保持し、後から呼び出すことができる特徴があります。

○サンプルコード5:クロージャを変数として保持する例

Swiftのクロージャは関数と似ているが、関数は名前を持ち、クロージャは名前を持たないという大きな違いがあります。

この特性を活かして、クロージャを変数や定数として保持することができます。

ただし、このようにクロージャを保持する場合、多くの場合で@escaping属性が必要になります。

なぜなら、関数やメソッドが終了した後でも、そのクロージャが外部の変数や定数によって保持されている場合、クロージャが「逃げる」(escape)可能性があるためです。

ここでは、クロージャを変数として保持するシンプルな例を紹介します。

class SampleClass {
    // @escapingを使用して、クロージャが関数の外部で保持されることを表す
    var completionHandler: (() -> Void)?

    func setCompletionHandler(handler: @escaping () -> Void) {
        self.completionHandler = handler
    }

    func executeCompletionHandler() {
        completionHandler?()
    }
}

このコードではSampleClassというクラス内に、クロージャ型の変数completionHandlerが定義されています。

setCompletionHandlerメソッドは、このcompletionHandler変数にクロージャをセットする役割を持ちます。

重要なのは、handler引数に@escaping属性が付与されている点です。

これにより、このクロージャが関数の外部で保持される可能性があることを示しています。

executeCompletionHandlerメソッドは、completionHandler変数にセットされたクロージャを実行する役割を持ちます。

このように、クロージャを変数や定数として保持する場面では、そのクロージャが「逃げる」可能性があるため、@escaping属性の使用が求められます。

実際に上記のクラスを使用してみると、次のような動作を期待することができます。

let sample = SampleClass()
sample.setCompletionHandler {
    print("クロージャが実行されました。")
}
sample.executeCompletionHandler()

この例を実行すると、”クロージャが実行されました。”という文字列がコンソールに出力されます。

○サンプルコード6:@escapingと@autoclosureの組み合わせ

Swiftでは、@escaping属性と@autoclosure属性を組み合わせることもできます。

@autoclosure属性は、引数を自動的にクロージャに変換する役割を持ちます。

この組み合わせを利用することで、引数として与えられた式を遅延評価することが可能となります。

ここでは、@escapingと@autoclosureの組み合わせを使用したサンプルコードの一例を紹介します。

func delayedPrint(_ message: @autoclosure @escaping () -> String) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        print(message())
    }
}

delayedPrint("1秒後に表示されます。")

この例では、delayedPrint関数は引数として与えられた文字列を、1秒後にコンソールに出力する役割を持ちます。

@autoclosure属性によって、引数として与えられた文字列は自動的にクロージャに変換され、@escaping属性によって、このクロージャが関数の外部で保持される可能性があることが表されています。

このコードを実行すると、”1秒後に表示されます。”という文字列がコンソールに出力されることを期待することができます。

●@escaping属性の応用例

Swiftの@escaping属性は、クロージャが関数の実行が終わった後にも継続して存在する必要がある場合に使用されます。

そのため、非同期処理やUIの操作、ネットワークリクエストといったタスクで頻繁に利用されるのが一般的です。

ここでは、そのようなシチュエーションでの@escapingの応用例を3つのサンプルコードを交えて詳細に解説していきます。

○サンプルコード7:非同期処理での@escapingの利用

非同期処理でよく利用されるDispatchQueueを使った例です。

このコードでは、非同期に実行されるタスク内でクロージャを使用しています。

import Foundation

func asyncFunction(completion: @escaping (String) -> Void) {
    DispatchQueue.global().async {
        // 非同期処理を行う
        sleep(2)

        // 処理が完了したらコールバックを呼ぶ
        completion("非同期処理完了!")
    }
}

asyncFunction { message in
    print(message)
}

この例では、非同期関数asyncFunction内で非同期処理を行っており、その結果を@escapingクロージャを通して呼び出し元に返しています。

非同期処理が終了した後、completionクロージャが実行され、”非同期処理完了!”というメッセージがコンソールに出力されます。

○サンプルコード8:UI操作と@escapingの組み合わせ

次に、UI操作と@escapingの組み合わせについて見ていきましょう。

このコードはUIKitを使用していますので、iOSアプリの開発環境で実行することを想定しています。

import UIKit

class ViewController: UIViewController {
    func animateView(_ view: UIView, completion: @escaping () -> Void) {
        UIView.animate(withDuration: 1.0, animations: {
            view.alpha = 0.0
        }, completion: { _ in
            completion()
        })
    }
}

let viewController = ViewController()
let sampleView = UIView()

viewController.animateView(sampleView) {
    print("アニメーション完了!")
}

この例では、animateView関数内でUIViewのアニメーションを実行し、アニメーションが終了した際に@escapingクロージャを通じて”アニメーション完了!”というメッセージがコンソールに出力されます。

○サンプルコード9:ネットワークリクエストと@escapingの利用

最後に、ネットワークリクエスト時に@escapingを使用する例を見ていきます。

このコードはURLSessionを利用しています。

import Foundation

func fetchData(from url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> Void) {
    let task = URLSession.shared.dataTask(with: url) { data, response, error in
        completion(data, response, error)
    }
    task.resume()
}

let sampleURL = URL(string: "https://example.com")!
fetchData(from: sampleURL) { data, response, error in
    if let error = error {
        print("エラー: \(error.localizedDescription)")
    } else if let data = data {
        print("取得データ: \(data)")
    }
}

この例では、指定されたURLからデータを非同期に取得しており、その結果を@escapingクロージャを通じて呼び出し元に返しています。

データ取得が完了すると、エラーがあればその内容、成功すれば取得したデータがコンソールに出力されます。

●注意点と対処法

Swiftの@escaping属性を使用する際、いくつか注意すべき点とその対処法があります。

特に、メモリリークのリスクやクロージャキャプチャに関する問題は、多くの開発者が直面する可能性があります。

これらの問題を適切に理解し、適切な対処を行うことで、安定したコードを実装することができます。

○メモリリークのリスクとその回避方法

@escaping属性を持つクロージャは、関数のスコープを超えて存在する可能性があります。

そのため、クロージャがクラスのインスタンスやオブジェクトをキャプチャすると、そのオブジェクトが不要になった後もメモリ上に残るリスクが高まります。

これは、メモリリークとして知られる問題です。

ここでは、メモリリークが発生する可能性のあるサンプルコードを紹介します。

class SampleClass {
    var name: String = "Sample"

    func performAction(with closure: @escaping () -> Void) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            closure()
        }
    }

    func printName() {
        performAction {
            print(self.name)
        }
    }
}

このコードでは、printNameメソッド内のクロージャがSampleClassのインスタンスをキャプチャしています。

このため、SampleClassのインスタンスが解放される前にクロージャが実行されると、メモリリークが発生するリスクが高まります。

メモリリークを回避するための対処法として、クロージャ内で使用するオブジェクトをweakもしくはunownedとして参照する方法があります。

下記のサンプルコードでは、[weak self]を使用してSampleClassのインスタンスを弱参照しています。

func printName() {
    performAction { [weak self] in
        print(self?.name ?? "Unknown")
    }
}

この例では、selfが解放された後でも、クロージャ内で安全に参照することができます。

○クロージャキャプチャの注意点

クロージャは、定義された場所のスコープ内の変数や定数をキャプチャすることができます。

しかし、キャプチャされた変数や定数の値がクロージャ実行時に変更されると、意図しない動作やエラーが発生する可能性があります。

下記のサンプルコードは、変数valueをキャプチャしたクロージャの例を表しています。

var value = 10
let incrementClosure = {
    value += 1
    print(value)
}

incrementClosure()  // 出力: 11
value = 20
incrementClosure()  // 出力: 21

この例では、クロージャが変数valueをキャプチャしています。

そのため、valueの値が変更された後のクロージャの実行では、新しいvalueの値が参照されます。

クロージャキャプチャの動作を正確に理解し、変数や定数の値が変更される可能性を考慮しながらコードを実装することが重要です。

●カスタマイズ方法

Swiftの@escaping属性は、関数やメソッドが終了した後もクロージャが生き続けることを示すためのものです。

しかし、場合によってはこの属性を独自の方法でカスタマイズして動作を変更したいことがあるかもしれません。

ここでは、@escaping属性をカスタマイズして独自の動作を実装する方法について説明します。

○@escaping属性をカスタマイズして独自の動作を実装する方法

まず、@escaping属性をカスタマイズするというのは、実際に属性自体を変更するわけではありません。Swiftの言語仕様として@escapingは固定されているため、変更や追加はできません。

しかし、カスタマイズという観点からは、@escapingを使ったコードの動作や振る舞いを独自に変更することは可能です。

以下のサンプルコードは、@escapingクロージャを用いた関数executeAfterDelayを表しています。

この関数は、指定された秒数の遅延後にクロージャを実行するものです。

func executeAfterDelay(seconds: Double, completion: @escaping () -> Void) {
    DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
        completion()
    }
}

このコードでは、DispatchQueue.main.asyncAfterを使って、指定された秒数後にクロージャを実行しています。

この例では、@escaping属性を持つクロージャcompletionを引数として受け取り、指定された遅延後に実行しています。

しかし、例えばクロージャの実行前に何か特定の処理を加えたい場合はどうでしょうか。

例として、クロージャが実行される前にログを出力する機能を追加してみましょう。

func executeWithLoggingAfterDelay(seconds: Double, completion: @escaping () -> Void) {
    DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
        print("クロージャを実行する前のログ")
        completion()
    }
}

こちらのコードでは、クロージャが実行される前にprint関数を用いてログを出力しています。

これにより、クロージャの実行をカスタマイズして、特定の前処理を追加することができました。

まとめ

Swiftにおける@escaping属性は、クロージャが関数やメソッドのスコープを超えて存在することを表す非常に重要な属性です。

この属性を使用することで、非同期処理やコールバック、変数としてのクロージャの保存など、多くの高度な操作が可能となります。

本記事を通じて、@escaping属性の基本的な使い方から応用例、注意点、さらにはカスタマイズの方法について詳細に解説しました。

Swiftを使用する上での非逃避クロージャと逃避クロージャの違いや、各シチュエーションでの適切な使用方法についての理解が深まったことでしょう。

特に非同期処理やUIの操作、ネットワークリクエストなどの現代のアプリ開発で頻繁に遭遇するシチュエーションでの@escapingの適切な使用は、アプリの品質やパフォーマンス向上に直結します。

また、メモリリークのリスクやクロージャキャプチャの注意点についても、深く理解することで、より安全なコードを書く手助けとなるでしょう。

今回の内容を参考に、Swiftの@escaping属性をより効果的に使用して、高品質なアプリ開発を進めていきましょう。