341
279

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Swift 3 の @escaping とは何か

Posted at

以前 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()
}

参考

341
279
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
341
279

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?