Help us understand the problem. What is going on with this article?

Swift 3 の @escaping とは何か

More than 3 years have passed since last update.

以前 Swiftの @noescape をもっと使おう という記事を書いたのですが、 Swift 3 からは @noescape の挙動がデフォルトに、そして@noescape の記述はdeprecatedになり、代わりに @escaping が追加されました。

使い方

引数で受け取るクロージャに対してattributeとして付与します。

class A {
    private let storedClosure: () -> ()

    init(closure: @escaping () -> ()) { // ここ
        storedClosure = closure
    }
}

どういうときに必要か

クロージャがスコープから抜けても存在し続けるときに @escaping が必要になります。
具体的には以下のような場合です。

  • クロージャがスコープ外で強参照されるとき
  • クロージャを非同期的に実行するとき

よってクロージャをすぐに実行し、どこからも強参照されない場合は @escaping は必要ありません。

class A {
    init(closure: () -> ()) { // ここ
        closure()
    }
}

escapingするとどうなるか

self. が必要

@escaping なクロージャ内でselfの変数やメソッドを使用する場合、selfをキャプチャすることを明示するため self. を付ける必要があります。

循環参照に気をつける

@escaping なクロージャはどこかから強参照される可能性があります。その参照元をクロージャ内で強参照すると循環参照になるので気をつけましょう。

特に、selfがプロパティとしてクロージャを保持 (強参照) する場合、クロージャ内で self を強参照すると循環参照になるので注意が必要です。

クロージャ内でオブジェクトを弱参照したい場合は、そのオブジェクトに対して weakunowned を付けます。

良いコード例

以下の例では [weak self] を使用することで循環参照を防いでいます。よって最終的に b オブジェクトが破棄され "deinit" が出力されます。

class A {
    private let storedClosure: () -> ()

    init(closure: @escaping () -> ()) {
        storedClosure = closure
    }
}

class B {
    private var a: A?
    private var count = 0

    func doSomething() {
        a = A(closure: { [weak self] in // selfを弱参照する
            self?.count += 1
        })
    }

    deinit {
        print("deinit") // 呼ばれる
    }
}

do {
    let b = B()
    b.doSomething()
}

悪いコード例

以下の例では、このようにして循環参照が発生します。

  1. b オブジェクトが a オブジェクトを保持 (強参照)
  2. a オブジェクトがクロージャを保持 (強参照)
  3. クロージャが self. として b オブジェクトをキャプチャして強参照

よって b オブジェクトは破棄されず "deinit" が出力されません。

class A {
    private let storedClosure: () -> ()

    init(closure: @escaping () -> ()) {
        storedClosure = closure
    }
}

class B {
    private var a: A?
    private var count = 0

    func doSomething() {
        a = A(closure: {
            self.count += 1 // selfを強参照している
        })
    }

    deinit {
        print("deinit") // 呼ばれない
    }
}

do {
    let b = B()
    b.doSomething()
}

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away