Posted at

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


参考