以前 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
を強参照すると循環参照になるので注意が必要です。
クロージャ内でオブジェクトを弱参照したい場合は、そのオブジェクトに対して weak
や unowned
を付けます。
良いコード例
以下の例では [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()
}
悪いコード例
以下の例では、このようにして循環参照が発生します。
-
b
オブジェクトがa
オブジェクトを保持 (強参照) -
a
オブジェクトがクロージャを保持 (強参照) - クロージャが
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()
}