2019/12/15 Swift クロージャー パート5(escaping closure)

Escaping Closures(エスケーピングクロージャー)

クロージャーが引数として関数に渡された時に、その関数が返り値を返して終了した後にクロージャーが呼び出されることを、「クロージャーは関数をエスケープした」と言います。

 クロージャーを引数の一つとして取る関数を定義するとき、クロージャーがエスケープできることを示すために@escaping属性を引数の型の前に記述することができます。

sample1-1

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"

completionHandlers.first?()
print(instance.x)
// Prints "100"

 

 クロージャーをエスケープさせる一つの方法は関数の外部に定義された変数に、クロージャーを代入(格納)することです。サンプルとして、非同期処理をスタートさせる多くの関数は完了時操作としてクロージャーを引数として受け取る。それらの(多くの)関数は非同期処理開始操作をスタートさせて返り値を返す(関数が終了する)が、引数として受け取ったクロージャーは、(関数が終了しても)非同期処理が完了する時まで呼び出されない。クロージャーを非同期処理完了時に呼び出すためにはクロージャーをエスケープさせる必要がある。

sample1-1ではdoSomething関数が非同期処理開始用関数、その中で実行されるsomeFunctionWithEscapingClosure関数が「非同期処理完了時処理用クロージャーを受け取り、非同期処理完了時に実行させるための処理をする関数」。比較としてエスケープをしないsomeFunctionWithNonescapingClosureを用意している。someFunctionWithNonescapingClosureは引数として受け取ったクロージャーを直ちに実行するだけの関数。

someFunctionWithNonescapingClosureはクロージャーをエスケープしていないので、自身の開始から終了までの間でしか引数として受け取ったクロージャーを呼び出すことはできない。なのでsample1-1では完了時処理として受け取ったクロージャーを開始時処理の時点で実行してしまっている。意図とは違う挙動になってしまっている。

それに対してsomeFunctionWithEscapingClosureでは引数として受け取ったクロージャーを外部の配列に追加(代入)することでエスケープしている。クロージャーは参照型なので、completionHandlersの要素がクロージャーへの参照を保持している間は(someFunctionWithEscapingClosureの実行が終了しても)削除されない。

その後非同期処理が完了した時点で、

completionHandlers.first?()

22行目のようにクロージャーを呼び出すことができるので、「非同期処理完了時処理を非同期処理完了時に実行したい」という意図通りの挙動を実現できる。

sample1-1で、someFunctionWithEscapingClosure関数は引数としてクロージャーを受け取り、関数外部に宣言されている配列completionHandlersに、受け取ったクロージャーを追加する。この引数に@escapingを付けないとコンパイルエラーとなる。

 クロージャーに@escapingを付ける、ということは、クロージャーの中で明示的にselfを参照しなければならない(selfを使わないといけない、ということ)、ということを意味します。

someFunctionWithEscapingClosureに渡されるクロージャーはエスケーピングクロージャー。つまりクロージャーの中で明示的にselfを使わないといけない、ということ。それに対して、someFunctionWithNonescapingClosureに渡されるクロージャーはエスケーピングクロージャーではない。つまりクロージャーの中でselfを使う必要がない(暗黙的にselfを参照する)

 

 

 

参考:

https://docs.swift.org/swift-book/LanguageGuide/Closures.html

コメントを残す

メールアドレスが公開されることはありません。