見出し画像

今でしょ!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 番目のクロージャは、ユーザにエラーを表示するエラーハンドラです。

(Picture) -> Void, onFailure: () -> Void) {
   if let picture = download("photo.jpg", from: server) {
   completion(picture)   ・・・ 最初のクロージャ
   } else {
    onFailure() ・・・ 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 でマークしなかった場合、コンパイルエラーが発生します。


いいなと思ったら応援しよう!