クロージャーの例
// 例1 // クロージャー1 let add = {(val1: Int, val2: Int) -> Int in return val1 + val2 } // クロージャー2 let sub = {(val1: Int, val2: Int) -> Int in return val1 - val2 } func exec(closure: (Int, Int) -> Int) { print(closure(3, 2)) } // クロージャーを渡す exec( closure: add ) exec( closure: sub ) // 例2 // クロージャー3 let percentString = {(val: Double) -> String in return String(val * 100) + "%" } // label.text = String(divide(16,4)) print(percentString(0.5)) // 例3 let completionClosure = {(val: Double) -> String in return String(val) + " Sec passed." } func bigTask(completion: (Double) -> (String)) { sleep(3) let endMessage = completion(3) print(endMessage) } // bigTask 実行 bigTask(completion: completionClosure)
クロージャーの例2
var closure1 = { (num1: Int, num2: Int) -> Int in return num1 + num2 } // 型推論による省略 var closure2 = { (num1: Int, num2: Int) in return num1 + num2 } // 単文の場合は return が省略できる var closure3 = { (num1: Int, num2: Int) in num1 + num2 } // さらに型推論による省略 var closure4 = { 1000 + 4 } // クロージャーをインライン実行 var x = { 1000 + 5 }() この例だと、まだ意味が推測できるが、次のようなコードを書かれると、パッと見た目に意味がわからず、読みづらい。 /// - Tag: MLModelSetup lazy var classificationRequest: VNCoreMLRequest = { do { let config = MLModelConfiguration() config.computeUnits = .all // let model = try VNCoreMLModel(for: CatDog100(configuration: config).model) let model1 = try SeaOfCloudClassifier1(configuration: config).model let model = try VNCoreMLModel(for: model1) let request = VNCoreMLRequest(model: model, completionHandler: { [weak self] request, error in self?.processClassifications(for: request as! VNCoreMLRequest, error: error) }) request.imageCropAndScaleOption = .centerCrop return request } catch { fatalError("Failed to load Vision ML model: \(error)") } }()
以下は、Swift Programming Language 5.2 より。
クロージャーは、コード内で受け渡し、使用できる機能の自己完結型ブロックです。Swiftのクロージャーは、CおよびObjective-Cのブロックや、他のプログラミング言語のラムダに似ています。
クロージャーは次の3つの形式のいずれかを取ります。
- グローバル関数は、名前があり、値をキャプチャしないクロージャです。
- ネストされた関数は、名前を持つクロージャーであり、内なる関数から値を取り込むことができます。
- クロージャ式は、軽量の構文で記述された名前のないクロージャであり、それを取り囲むコンテキストから値をキャプチャできます
クロージャ式の構文
クロージャ式の構文は、次の一般的な形式を持っています。in
キーワードによってクロージャーの本体が始まります。
{ (parameters) -> return type in statements }
次は、sorted(by:)
メソッドをクロージャー式で簡潔に表現した例に説明します。
// sorted メソッド // Swiftの標準ライブラリは、sorted(by:)メソッドを提供します。 let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] // 2要素の大小比較をする関数表記 func backward(_ s1: String, _ s2: String) -> Bool { return s1 > s2 } let r1 = names.sorted(by: backward) print(r1) // これをクロージャー式で表すと次のように書ける let r2 = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 }) print(r2) // さらに型を推論できるので、型を省略する let r3 = names.sorted(by: { s1, s2 in return s1 > s2 } ) print(r3) // $0, $1 を使い、引数名を省略できる let r4 = names.sorted(by: {$0 > $1 }) print(r4) // さらに、Operator Method により、ここまで簡素化できる let r5 = names.sorted(by: > ) print(r5)
Trailing クロージャー
// 次のクロージャーを Trailing クロージャーへ変換する let r4 = names.sorted(by: {$0 > $1 }) print(r4) // クロージャ式が関数またはメソッドの唯一の引数の場合、括弧 () を省略できます。 // Trailing クロージャー let r6 = names.sorted{$0 > $1 } print(r6)
値のキャプチャ
クロージャは、それが定義されている周囲のコンテキストから定数と変数をキャプチャできます。その後、定数と変数を定義した元のスコープが存在しなくても、クロージャーはその本体内からこれらの定数と変数の値を参照および変更できます。
Swiftでは、値をキャプチャできる最も単純なクロージャの形式は、別の関数の本体内に記述されたネストされた関数です。ネストされた関数は、その外部関数の引数をキャプチャでき、外部関数内で定義されている定数と変数もキャプチャできます。
これは、makeIncrementerという関数の例です。これには、incrementerというネストされた関数が含まれています。ネストされたincrementer()
関数は、周囲のコンテキストから2つの値runningTotalとamountを取得します。これらの値を捕捉した後、クロージャーincrementerによってrunningTotalが返される。
func makeIncrementer(forIncrement amount: Int) -> () -> Int { var runningTotal = 0 func incrementer() -> Int { runningTotal += amount return runningTotal } return incrementer } let incrementByTen = makeIncrementer(forIncrement: 10)
この例では、呼び出されるたびに変数に10
追加されるインクリメンター関数を参照するために呼び出される定数を設定します。
incrementByTen() // 10 incrementByTen() // 20 incrementByTen() // 30 let incrementBySeven = makeIncrementer(forIncrement: 7) incrementBySeven() // 7 incrementByTen を再度呼び出すと、その runningTotal変数がインクリメントされるが、incrementBySevenによりキャプチャされた変数には影響しません incrementByTen() // 40
注意
クラスインスタンスのプロパティにクロージャーを割り当て、クロージャーがインスタンスまたはそのメンバーを参照することによってそのインスタンスをキャプチャする場合、クロージャーとインスタンスの間に強い参照サイクルが作成されます。Swiftはキャプチャリストを使用して、これらの強力な参照サイクルを壊します。
クロージャは参照型です
上記の例ではincrementBySeven
、incrementByTen
は定数ですが、これらの定数が参照するクロージャrunningTotal
は、キャプチャした変数をインクリメントできます。これは、関数とクロージャが参照型であるためです。
let alsoIncrementByTen = incrementByTen alsoIncrementByTen() // returns a value of 50 incrementByTen() // returns a value of 60
クロージャーのエスケープ
関数の外部で定義されている変数にクロージャーを格納する場合、エスケープが必要になります。
非同期操作を開始する多くの関数は、完了ハンドラーとしてクロージャー引数を取ります。関数は、操作の開始後に戻りますが、操作が完了するまでクロージャーは呼び出されません。この場合、関数の外側でクロージャが格納されるため、クロージャーはエスケープする必要があります。
// 実行部 let instance = SomeClass() instance.doSomething() print(instance.x) // Prints "200" completionHandlers.first?() print(instance.x) // Prints "100" var completionHandlers: [() -> Void] = [] class SomeClass { var x = 10 func doSomething() { // エスケープクロージャーは、明示的にselfを参照する必要があります。 escapingClosure { self.x = 100 } // ノンエスケープクロージャーは、暗黙的にselfに参照できます。 nonescapingClosure { x = 200 } } // クロージャーの外側の completionHandlers に追加する場合 // エスケープしないとコンパイル時エラーになる func escapingClosure(completionHandler: @escaping () -> Void) { completionHandlers.append(completionHandler) } func nonescapingClosure(closure: () -> Void) { closure() } }
オートクロージャー
autoclosureは、明示的なクロージャーの代わりに通常の式を記述することで、関数のパラメーターの中括弧を省略できます。
オートクロージャーを使用している例
assert(condition:message:file:line:)
関数はそのcondition
と message
パラメータのオートクロージャーを取得します。そのcondition
パラメーターは、デバッグビルドで評価され、そのmessage
パラメータがconditionがfalseの場合にのみ評価されています。
クロージャーは呼び出すまで実行されないため、自動クロージャーを使用すると、評価を遅らせることができます。評価の遅延は、コードが評価されるタイミングを制御できるため、副作用があるか計算コストがかかるコードに役立ちます。
// autoclosure ではない場合 func serve1(customer customerProvider: () -> String) { print("Now serving \(customerProvider())!") } // @autoclosure で宣言する場合 func serve2(customer customerProvider: @autoclosure () -> String) { print("Now serving \(customerProvider())!") } var customers = ["1st", "2nd", "3rd", "4th"] // autoclosure ではない場合、呼び出しには中括弧が必要 serve1(customer: { customers.remove(at: 0) } ) // Prints "Now serving 1st" // @autoclosure で宣言する場合、中括弧を省略できる serve2(customer: customers.remove(at: 0)) // Prints "Now serving 2nd"
注意
自動クロージャを使いすぎると、コードが理解しにくくなる可能性があります。コンテキストと関数名は、評価が延期されていることを明確にする必要があります。
エスケープを許可するオートクロージャーが必要な場合は@autoclosure
属性と@escaping属性の両方を使用します。
// customersInLine is ["Barry", "Daniella"] var customerProviders: [() -> String] = [] func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) { customerProviders.append(customerProvider) } collectCustomerProviders(customersInLine.remove(at: 0)) collectCustomerProviders(customersInLine.remove(at: 0)) print("Collected \(customerProviders.count) closures.") // Prints "Collected 2 closures." for customerProvider in customerProviders { print("Now serving \(customerProvider())!") } // Prints "Now serving Barry!" // Prints "Now serving Daniella!"
上記のコードでは、customerProvider
引数として渡されたクロージャーを呼び出す代わりに、collectCustomerProviders(_:)
関数はクロージャーをcustomerProviders
配列に追加します。配列は関数のスコープ外で宣言されています。つまり、関数が戻った後で、配列内のクロージャーを実行できます。その結果、customerProvider
引数の値は、関数のスコープをエスケープできるようにする必要があります。