非同期処理も「スコープ外の変数に保存する」のと同じであることを理解するのが良さそうな気がします。
@escaping
が必要になるのは、クロージャーが内側にキャプチャー(クロージングオーバー)する変数が、通常のスコープを超えて存在する可能性がある場合に必要になります。
非同期処理を行うメソッドにクロージャーを渡す場合、別のスレッドにクロージャーを渡した後に、それが実行されるのを待たずに現在のスコープが終了します。このとき渡したクロージャーは、別のスレッドで処理を実行するために、渡された側が「元のスコープ外の変数に保存しておく」ことになります。そうしないと、別スレッドでそのクロージャーを実行する前に、元のスコープが終わって破棄されてしまう可能性が出てきてしまうため、実行先のスレッドで、そのクロージャーを使い終わるまでの間は保存しておく必要があります。
Swift の非同期処理ライブラリー Dispatch
の sync
メソッドと async
メソッドの定義の違いももしかすると何かの参考になるかもしれないので、説明用に要所だけ切り抜いた形で挙げておきます。
swift
1class DispatchQueue {
2 /*
3 非同期処理は @escaping として
4 クロージャーを受け取る設計になっています。
5 非同期処理は、このスコープが終わった後に実行タイミングがくる
6 可能性があるため、渡したクロージャーがこのスコープを抜けても
7 生存しているように、外部に保存しておく必要があるためです。
8 */
9 public func async(execute work: @escaping @convention(block) () -> Void)
10
11 /*
12 同期処理は @escaping なしで
13 クロージャーを受け取る設計になっています。
14 非同期処理なら、その処理が終わるまでこのスコープを
15 抜けることがないため、外部に保存しておく必要がありません。
16 */
17 public func sync<T>(execute work: () throws -> T) rethrows -> T
18}
swift
1func postMessage() {
2
3 // このクロージャーのスコープは `postMessage` 関数内のブロックです。
4 let message: () -> Void = {
5 }
6
7 /*
8 非同期的な実行により `message` がこのスコープを
9 抜けた後に実行される可能性が「ある」ため `@escaping` として渡されます。
10 */
11 messageQueue.async(execute: message)
12
13 /*
14 同期的な実行により `message` がこのスコープを
15 抜けた後に実行される可能性が「ない」ため `@noescape` として渡されます。
16 */
17 messageQueue.sync(execute: message)
18}