English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Swift 閉包

この記事では、例を通じてクロージャとは何か、文法、Swiftでのクロージャのタイプについて学びます。

Swift関数記事で、funcキーワードを使用して関数を作成しました。しかし、Swiftにはfuncキーワードと関数名を使用せずに定義できる特別な関数タイプ、クロージャがあります。
関数のように、クロージャは引数を受け取り、値を返すことができます。また、呼び出された後に実行される一連のステートメントを含んでおり、変数に関数として割り当てることができます/定数。

SwiftでのクロージャはCとObjective-Cでのコードブロック(ブロック)や他のプログラミング言語でのアノニマス関数は非常に似ています。

全局関数とネスト関数は特別なクロージャです。

闭包的形式有:

クローズドの形式は:グローバル関数クローズド表現
ネストされた関数名前付きクローズド、値をキャプチャーできません。名前付きクローズド、封闭関数内の値をキャプチャーできます。

無名クローズド、軽量な文法を使用し、コンテキスト環境に基づいて値をキャプチャーできます。

  • Swiftのクローズドには多くの最適化があります:

  • コンテキストからパラメータと返値の型を推測します

  • 単一行式クローズドから暗黙的に返されます(つまりクローズド体が一行の代码のみで、returnを省略できます)1パラメータ名を簡略化することができます、例えば$0、$

  • トレリングクローズド文法(Trailing closure syntax)を提供しています(0から始まり、i番目の引数を示します...)

文法

以下は、引数を受け取り、指定された型を返すクローズドの文法を定義しています:

{(parameters) -> return type in
   statements
}

オンラインサンプル

let simpleClosure = {
    print("Hello, World!")
}
simpleClosure()

以下のプログラムの実行結果は:

Hello, World!

以下のクローズド形式は2つの引数を受け取り、ブール値を返します:

{(Int, Int) -> Bool in
   Statement1
   Statement 2
    ---
   Statement n
}

オンラインサンプル

let simpleClosure:(String) -> (String) = { name in
    
    let greeting = "Hello, World!" + "Program"
    return greeting
}
let result = simpleClosure("Hello, World")
print(result)

以下のプログラムの実行結果は:

Hello, World! Program

クローズド表現

クローズド表現は、簡潔な構文を使用してインラインクローズドを構築する方法です。クローズド表現は、クローズドを書くことを簡単で明瞭にするいくつかの構文の最適化を提供します。

sortedメソッド

Swiftの標準ライブラリには、 sorted(by:) のメソッドは、ソートするために提供するクローズド関数に基づいて既知の型の配列の値をソートします。

ソートが完了すると、sorted(by:)メソッドは元の配列と同じサイズの、同じ型の要素を含み、要素が正しくソートされた新しい配列を返します。元の配列はsorted(by:)メソッドによって変更されません。

sorted(by:)メソッドには2つの引数を渡す必要があります:

  • 既知の型の配列

  • クローズド関数、このクローズド関数は配列の要素の型と同じ2つの値を渡し、ソートが終わった後に最初の引数が二番目の引数の前にあるか後にあるかを示すブール型の値を返します。最初の引数の値が二番目の引数の値の前に現れる場合、ソートクローズド関数は以下のように返さなければなりません。 true、その逆に返す false

オンラインサンプル

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)が第二の文字列(s2)、backwards関数がtrueを返し、新しい配列でs1に現れるべきです2前の文字列の文字に対して、"大于"は"アルファベット順に後に出現する"を意味します。これは、文字"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は、クローズャ内の最初と第二のString型のパラメータを表します。

以下のプログラムの実行結果は:

["S", "D", "BE", "AT", "AE"]

クローズャ式でパラメータ名の短縮を使用している場合、クローズャのパラメータリストからその定義を省略することができます。対応するパラメータ名の短縮の型は、関数の型から推測されます。inキーワードも省略できます。

演算子関数

実際には、上記の例のクローズャ式をより短く書く方法があります。

SwiftのStringの型が定義しており、>)の文字列実装として、それが関数を受け取り、二つのStringの引数をとり、返すBoolの値に一致しています。 そしてこれはsort(_:)メソッドの第二引数が必要な関数の型が一致しています。 したがって、あなたは単に大于号を渡すことができ、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
}

Int型の引数を持つ関数makeIncrementorがあり、外部パラメータ名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