Swift クロージャ
クロージャ(Closures)は、自己完結型の機能コードブロックであり、コード内で使用したり、パラメータとして値を渡すために使用したりできます。
Swift のクロージャは、C や Objective-C のブロック(blocks)、および他のいくつかのプログラミング言語の無名関数に似ています。
グローバル関数とネスト関数は、実際には特別なクロージャです。
クロージャには次の形式があります:
| グローバル関数 | ネスト関数 | クロージャ式 |
| 名前を持ちますが、値をキャプチャすることはできません。 | 名前を持ち、囲んでいる関数内の値をキャプチャすることもできます。 | 名前のないクロージャで、軽量な構文を使用し、コンテキストに応じて値をキャプチャできます。 |
Swift のクロージャには、多くの最適化があります:
- コンテキストからパラメータと戻り値の型を推論する
- 単行式クロージャから暗黙的に返す(つまり、クロージャ本体が1行のコードだけの場合、returnを省略できる)
- 簡略化されたパラメータ名を使用できます。例:$0, $1(0から始まり、i番目のパラメータを表します...)
- トレイリングクロージャ構文(Trailing closure syntax)を提供します
- 既知の型の配列
- クロージャ関数。このクロージャ関数は、配列の要素と同じ型の2つの値を受け取り、ソートが終了したときに最初のパラメータが2番目のパラメータの前にあるか後ろにあるかを示すブール値を返す必要があります。最初のパラメータの値が2番目のパラメータの値より前に現れる場合、ソート用クロージャ関数は返す必要がありますtrue、逆の場合は返しますfalse。
構文
以下は、パラメータを受け取り、指定された型を返すクロージャ構文の定義です:
{(parameters) -> return type in
statements
}
例
import Cocoa
let studname = { print("Swift 闭包实例。") }
studname()
上記のプログラムを実行すると、出力結果は次のとおりです:
Swift 闭包实例。
次のクロージャ形式は、2つのパラメータを受け取り、ブール値を返します:
{(Int, Int) -> Bool in
Statement1
Statement 2
---
Statement n
}
例
import Cocoa
let divide = {(val1: Int, val2: Int) -> Int in
return val1 / val2
}
let result = divide(200, 20)
print (result)
上記のプログラムを実行すると、出力結果は次のとおりです:
10
クロージャ式
クロージャ式は、簡潔な構文を使用してインラインクロージャを構築する方法です。クロージャ式はいくつかの構文上の最適化を提供し、クロージャの記述を簡単で明確にします。
sorted メソッド
Swift 標準ライブラリは、次の名前のsorted(by:)メソッドを提供します。このメソッドは、提供されたソート用のクロージャ関数に基づいて、既知の型の配列内の値をソートします。
ソートが完了すると、sorted(by:) メソッドは元の配列と同じサイズで、同じ型の要素を含み、要素が正しくソートされた新しい配列を返します。元の配列は sorted(by:) メソッドによって変更されません。
sorted(by:) メソッドには、2つのパラメータを渡す必要があります:
例
import Cocoa
let names = ["AT", "AE", "D", "S", "BE"]
// 使用普通函数(或内嵌函数)提供排序功能,闭包函数类型需为(String, String) -> Bool。
func backwards(s1: String, s2: String) -> Bool {
return s1 > s2
}
var reversed = names.sorted(by: backwards)
print(reversed)
上記のプログラムを実行すると、出力結果は次のとおりです:
["S", "D", "BE", "AT", "AE"]
最初の文字列 (s1) が2番目の文字列 (s2) より大きい場合、backwards 関数は true を返し、新しい配列では s1 が s2 の前に現れるべきであることを示します。文字列内の文字に関して、"大きい" は "アルファベット順で後ろに現れる" ことを意味します。つまり、文字 "B" は文字 "A" より大きく、文字列 "S" は文字列 "D" より大きいことになります。これによりアルファベットの逆順でソートされ、"AT" は "AE" の前に配置されます。
パラメータ名の省略
Swift はインライン関数にパラメータ名の省略機能を自動的に提供します。$0, $1, $2 を使ってクロージャのパラメータを順番に呼び出すことができます。
例
import Cocoa
let names = ["AT", "AE", "D", "S", "BE"]
var reversed = names.sorted( by: { $0 > $1 } )
print(reversed)
$0 と $1 は、クロージャ内の1番目と2番目の String 型のパラメータを表します。
上記のプログラムを実行すると、出力結果は次のとおりです:
["S", "D", "BE", "AT", "AE"]
クロージャ式でパラメータ名の省略を使用する場合、クロージャのパラメータリストでの定義を省略でき、対応するパラメータ名の省略の型は関数型から推論されます。in キーワードも同様に省略できます。
演算子関数
実際には、上記の例のクロージャ式を記述するための、より簡潔な方法もあります。
Swift のStringString 型は、大なり記号 (>) の文字列実装を定義しており、それは関数として2つのString型のパラメータを受け取り、Bool型の値を返します。これはまさに、sort(_:)メソッドの2番目のパラメータが必要とする関数型と一致します。したがって、大なり記号を渡すだけで、Swiftは大なり記号の文字列関数実装を使用したいことを自動的に推論できます。
import Cocoa let names = ["AT", "AE", "D", "S", "BE"] var reversed = names.sorted(by: >) print(reversed)
上記のプログラムを実行すると、出力結果は次のとおりです:
["S", "D", "BE", "AT", "AE"]
トレイリングクロージャ
トレイリングクロージャは、関数の括弧の後に記述するクロージャ式であり、関数はそれを最後のパラメータとして呼び出すことをサポートします。
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函数体部分
}
// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure({
// 闭包主体部分
})
// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
// 闭包主体部分
}
例
import Cocoa
let names = ["AT", "AE", "D", "S", "BE"]
//尾随闭包
var reversed = names.sorted() { $0 > $1 }
print(reversed)
sort() の後の { $0 > $1 } がトレイリングクロージャです。
上記のプログラムを実行すると、出力結果は次のとおりです:
["S", "D", "BE", "AT", "AE"]
注意: 関数がクロージャ式を1つだけ必要とする場合、トレイリングクロージャを使用するときには、次のものさえも
()省略することができます。reversed = names.sorted { $0 > $1 }
値のキャプチャ
クロージャは、定義されたコンテキスト内で定数や変数をキャプチャできます。
これらの定数や変数が定義された元のスコープが存在しなくなっても、クロージャはクロージャ関数本体でこれらの値を参照および変更できます。
Swift で最も単純なクロージャ形式はネスト関数です。つまり、他の関数の関数本体内で定義される関数です。
ネスト関数は、外部関数のすべてのパラメータ、および定義された定数や変数をキャプチャできます。
この例を見てください:
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
関数 makeIncrementor は、Int 型のパラメータ amout を持ち、外部パラメータ名は forIncremet です。つまり、呼び出すときにはこの外部名を使用する必要があります。戻り値は、()-> Intの関数です。
関数本体では、変数 runningTotal と関数 incrementor が宣言されています。
incrementor 関数はパラメータを取得しませんが、関数本体で runningTotal と amount 変数にアクセスします。これは、それを含む関数本体内にすでに存在する runningTotal と amount 変数をキャプチャすることによって実現されます。
amount 変数を変更しないため、incrementor は実際にはその変数のコピーをキャプチャして保存し、そのコピーは incrementor とともに保存されます。
したがって、この関数を呼び出すたびに加算されていきます:
import Cocoa
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
let incrementByTen = makeIncrementor(forIncrement: 10)
// 返回的值为10
print(incrementByTen())
// 返回的值为20
print(incrementByTen())
// 返回的值为30
print(incrementByTen())
上記のプログラムを実行すると、出力結果は次のとおりです:
10 20 30
クロージャは参照型です
上記の例では、incrementByTen は定数ですが、これらの定数が指すクロージャは、キャプチャした変数の値を増やすことができます。
これは、関数とクロージャがどちらも参照型であるためです。
関数/クロージャを定数に代入するか変数に代入するかに関わらず、実際には定数/変数の値を対応する関数/クロージャの参照に設定しています。上記の例では、incrementByTen が指すクロージャの参照は定数であり、クロージャの内容そのものではありません。
これは、クロージャを2つの異なる定数/変数に代入した場合、両方の値が同じクロージャを指すことも意味します:
import Cocoa
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
let incrementByTen = makeIncrementor(forIncrement: 10)
// 返回的值为10
incrementByTen()
// 返回的值为20
incrementByTen()
// 返回的值为30
incrementByTen()
// 返回的值为40
incrementByTen()
let alsoIncrementByTen = incrementByTen
// 返回的值也为50
print(alsoIncrementByTen())
上記のプログラムを実行すると、出力結果は次のとおりです:
50その他の拡張