今でしょ!Swift - クロージャ 2
末尾クロージャ
例えば
reversedNames = names.sorted() { $0 > $1 }
これは
reversedNames = names.sorted { $0 > $1 }
ともできます。これが末尾クロージャと呼ばれるものです。要するに"( )"を省き直接"{ }"の中に処理を書くことができます。
例示です。
let digitNames = [
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]
以下、変数numbersの配列の値について、変数 digitNamesで定義している文字を利用して表示させる関数(クロージャで)になります。
map関数を使ってすれぞれの数字にあった文字を当てていきます。
let strings = numbers.map { (number) -> String in
var number = number
var output = ""
repeat {
output = digitNames[number % 10]! + output
number /= 10
} while number > 0
return output
}
// `strings` は [String] 型に推論されます
// 値は ["OneSix", "FiveEight", "FiveOneZero"]
関数が複数のクロージャを受け取る場合は、最初の末尾クロージャの引数ラベルを省略し、残りの末尾クロージャにラベルを付けます。例えば、次の関数はフォトギャラリの画像を読み込みのコードの例示。
func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
if let picture = download("photo.jpg", from: server) {
completion(picture)
} else {
onFailure()
}
}
この関数を呼び出して画像をロードするときは、2 つのクロージャを渡します。
最初のクロージャは、ダウンロードが成功した後に画像を表示する完了ハンドラです。
2 番目のクロージャは、ユーザにエラーを表示するエラーハンドラです。
loadPicture(from: someServer) { picture in
someView.currentPicture = picture
} onFailure: {
print("画像をダウンロードできませんでした。")
}
この例では、loadPicture(from:completion:onFailure:) 関数がネットワークタスクをバックグラウンドに非同期処理し、ネットワークタスクが終了すると 2 つの処理のいずれかを呼び出します。
このように作成すると、両方の状況を処理する 1 つのクロージャを使用する代わりに、ネットワーク障害の処理を担当するコードと、ダウンロードが成功した後にユーザーインターフェイスを更新するコードを、明確に分けることができます
値のキャプチャ
クロージャは、定義されている関数内の定数と変数を一時保存できます。クロージャは、定数と変数を定義した元の関数が存在しなくなった場合でも、本文内からそれらの定数と変数の値を参照および変更できます。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
中の関数を取り出してみます。
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
この関数は引数はありませんが上位の関数の引数amountを使い変数runningTotalに加算していきます。この処理についてキャプチャとよびます。
これを以下実行すると
let incrementByTen = makeIncrementer(forIncrement: 10)
結果は
incrementByTen()
// 10 を返します
incrementByTen()
// 20 を返します
incrementByTen()
// 30 を返します
足されていきます。
違う変数に入れて実行すると
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// 7 を返します
違った処理として実行されます。
incrementByTen()
// 40 を返します
最初の処理は影響されません。
クロージャは参照型
上記の例では、incrementBySeven と incrementByTen は定数ですが、これらの定数が参照するクロージャは、キャプチャした runningTotal 変数をインクリメントすることができます。関数とクロージャが参照型だからです。
関数またはクロージャを定数または変数に代入するときはいつでも、実際にはその定数または変数を、関数またはクロージャへの参照として設定しています。上記の例では、クロージャ自体の内容ではなく、incrementByTen が定数を参照するのは、クロージャの選択です。
つまり、2 つの異なる定数または変数にクロージャを代入する場合、それらは同じクロージャを参照することを意味します。
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// 50 を返します
incrementByTen()
// 60 を返します
上記の例は、alsoIncrementByTen を呼び出すことは incrementByTen を呼び出すことと同じだということを示しています。どちらも同じクロージャを参照しているため、両方とも同じ runningTotal をインクリメントして結果を返します。
エスケープクロージャ
関数の引数として渡されたクロージャが、関数本文が終了した後に呼び出される場合、関数をエスケープすると呼ばれています。
パラメータの型の前に @escaping を記述して、クロージャがエスケープすることを示すことができます。
例示
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
someFunctionWithEscapingClosure(_:) 関数は、引数としてクロージャを取り、関数の外部で宣言されている配列に追加します。この関数のパラメータを @escaping でマークしなかった場合、コンパイルエラーが発生します。