Enumeration

構文

enum SomeEnumeration {
    // enumeration definition goes here
}

// 例
enum CompassPoint {
    case north
    case south
    case east
    case west
}

// 注意
// Cなどと異なり、デフォルトで整数値は設定されない。代わりに、明示的に定義されたCompassPoint型

// カンマで区切って、複数のケースを1行に表示できます。
enum Planet {
    case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}

// enum の名前は大文字で始め、単数形で表記すること

var directionToHead = CompassPoint.west
// directionToHeadの型は、CompassPoint型として推測できる
// したがって、値を設定するときに型を省略できます。
directionToHead = .east

Switchステートメントを使用した列挙値のマッチング

directionToHead = .south
switch directionToHead {
case .north:
    print("Lots of planets have a north")
case .south:
    print("Watch out for penguins")
case .east:
    print("Where the sun rises")
case .west:
    print("Where the skies are blue")
}
// Prints "Watch out for penguins"

enum caseの列挙

// CaseIterable 型を指定することにより、allCases で全てのケースを列挙できる
enum Beverage: CaseIterable {
    case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// Prints "3 beverages available"

for beverage in Beverage.allCases {
    print(beverage)
}
// coffee
// tea
// juice

関連する値

UPCバーコードは4つの整数のタプル、QRコードバーコードを任意の長さの文字列として保存すると便利です。

// UPCバーコードとQRコードバーコードの両方を次のように表すことができる
enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}

var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")

switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
    print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
    print("QR code: \(productCode).")
}
// Prints "QR code: ABCDEFGHIJKLMNOP."

Raw Value

// Raw Value の設定方法
enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}

Raw Valueは、文字列、文字、または整数または浮動小数点型のいずれかで、enum 宣言内で一意である必要があります。

暗黙的に割り当てられた Raw Value

整数または文字列の未加工値を格納する列挙型を使用している場合、各ケースに未加工値を明示的に割り当てないと、自動的に値を割り当てます。

たとえば、生の値に整数が使用されている場合、各ケースの暗黙的な値は前のケースよりも1つ多くなります。最初のケースに値が設定されていない場合、その値は0です。

以下の列挙はPlanet、太陽からの各惑星の順序を表す整数の生の値を使用して、以前の列挙を改良したものです。

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
// この場合、Planet.venusは2が割り当てられます。

enum CompassPoint: String {
    case north, south, east, west
}
// この場合、CompassPoint.south は "south"が割り当てられます。

let earthsOrder = Planet.earth.rawValue
// earthsOrder is 3

let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection is "west"

Raw Value からの初期化

let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet is of type Planet? and equals Planet.uranus
// すべての可能な値が一致する惑星を見つけるわけではありません。このため、Raw Value の初期化子は常にオプショナルの Enum型のケースを返します。

// 例えば、Raw Value が 11 の惑星は nil が返る
let positionToFind = 11
if let somePlanet = Planet(rawValue: positionToFind) {
    switch somePlanet {
    case .earth:
        print("Mostly harmless")
    default:
        print("Not a safe place for humans")
    }
} else {
    print("There isn't a planet at position \(positionToFind)")
}
// Prints "There isn't a planet at position 11"

再帰的 enum

indirectにより、再帰的な enum を表すことができる。

// 単純な算術式を表す再帰的 enum
enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

// すべてのケースに対して indirect enum を宣言することもできる
indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}

// (5 + 4) * 2 を再帰的 enum で表した例
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

// 評価する関数の例
func evaluate(_ expression: ArithmeticExpression) -> Int {
    switch expression {
    case let .number(value):
        return value
    case let .addition(left, right):
        return evaluate(left) + evaluate(right)
    case let .multiplication(left, right):
        return evaluate(left) * evaluate(right)
    }
}

print(evaluate(product))
// Prints "18"

この関数は、関連付けられた値を返すだけで、単純な数値を評価します。左側の式を評価し、右側の式を評価してから、それらを加算または乗算することにより、加算または乗算を評価します。

クロージャー

クロージャーの例

        // 例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はキャプチャリストを使用して、これらの強力な参照サイクルを壊します

クロージャは参照型です

上記の例ではincrementBySevenincrementByTenは定数ですが、これらの定数が参照するクロージャ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引数の値は、関数のスコープをエスケープできるようにする必要があります。

関数

関数の定義と呼び出し

func greet(person: String) -> String {
    let greeting = "Hello, " + person + "!"
    return greeting
}

関数パラメーターと戻り値

// パラメータなしの関数
func sayHelloWorld() -> String {
    return "hello, world"
}

// 複数のパラメーターを持つ関数
func greet(person: String, alreadyGreeted: Bool) -> String {
  if alreadyGreeted {
    return greetAgain(person: person)
  } else {
    return greet(person: person)
  }
}

// 戻り値のない関数
func greet(person: String) {
  print("Hello, \(person)!")
}

注意

厳密に言えば、戻り値が定義されていなくても、関数値を返します。戻り値の型が定義されていない関数は、typeの特別な値 void を返します。これは単に空のタプル()です。

複数の戻り値を持つ関数

関数の戻り値の型としてタプル型を使用して、複数の値を返すことができます。

func minMax(array: [Int]) -> (min: Int, max: Int) {
  var currentMin = array[0]
  var currentMax = array[0]
  for value in array[1..<array.count] {
    if value < currentMin {
      currentMin = value
    } else if value > currentMax {
    currentMax = value
    }
  }
  return (currentMin, currentMax)
}

let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
print("min is \(bounds.min) and max is \(bounds.max)")

オプショナルタプルの戻り値

func minMax(array: [Int]) -> (min: Int, max: Int)? {
    if array.isEmpty { return nil }
    var currentMin = array[0]
    var currentMax = array[0]
    for value in array[1..<array.count] {
        if value < currentMin {
            currentMin = value
        } else if value > currentMax {
            currentMax = value
        }
    }
    return (currentMin, currentMax)
}

注意

タプル全体がオプショナルの(Int, Int)?と、個々のオプショナルの(Int?, Int?)とは意味が異なる。

暗黙的な戻り値を持つ関数

関数の本体全体が単一の式である場合、関数は暗黙的にその式を返します。たとえば、以下の両方の関数は同じ動作をします。

func greeting(for person: String) -> String {
    // return がない、暗黙的な戻り値
    "Hello, " + person + "!"
}
print(greeting(for: "Dave"))

func anotherGreeting(for person: String) -> String {
    return "Hello, " + person + "!"
}
print(anotherGreeting(for: "Dave"))

関数の引数ラベルとパラメーター名

各関数パラメーターには、引数ラベルパラメーター名の両方があります

// 関数の実装で使用されるとき、パラメータ名
func someFunction(firstParameterName: Int, secondParameterName: Int) {
}

// 関数を呼び出すとき、引数ラベル
someFunction(firstParameterName: 1, secondParameterName: 2)

// 引数ラベルを使用すると、読みやすく意図が明確な関数本体を提供しながら、表現力豊かな文のように関数を呼び出すことができます。

// 引数ラベルの省略
// パラメータの引数ラベルが不要_な場合は、そのパラメータの明示的な引数ラベルの代わりにアンダースコア()を記述します。
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
}
someFunction(1, secondParameterName: 2)
// パラメータに引数ラベルがある場合、関数を呼び出すときに引数にラベルを付ける必要があります。

// デフォルトのパラメータ値
// パラメータのタイプの後にパラメータに値を割り当てることにより、関数内の任意のパラメータのデフォルト値を定義できます。デフォルト値が定義されている場合は、関数を呼び出すときにそのパラメーターを省略できます。
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
}
someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) 
someFunction(parameterWithoutDefault: 4) // parameterWithDefault is 12
// デフォルト値を持たないパラメーターを最初に配置します。通常、デフォルト値を持たないパラメータは、デフォルト値を持たないパラメーターより重要です。

// 可変パラメータ
// ...により、可変パラメーターを記述します。
// 関数には、可変引数は1つしか含めることができません。
func arithmeticMean(_ numbers: Double...) -> Double {
    var total: Double = 0
    for number in numbers {
        total += number
    }
    return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// returns 3.0, which is the arithmetic mean of these five numbers
arithmeticMean(3, 8.25, 18.75)
// returns 10.0, which is the arithmetic mean of these three numbers

// 入出力パラメーター
// 関数パラメーターは、デフォルトでは定数です。関数の本体内から関数パラメーターの値を変更しようとすると、コンパイル時エラーが発生します。つまり、誤ってパラメーターの値を変更することはできません。
// パラメータのタイプの直前に inout キーワードを配置することで、入出力パラメータを記述します。入出力パラメーターは、関数によって値が変更され、関数から戻されます。
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

// アンパサンド(&)を変数として引数に入力パラメータとして渡す場合は、関数で変更できることを示します。
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"

// inout 入出力パラメータにデフォルト値を設定することはできません。


// swapTwoInts(_:_:)関数は、単にaとbの値をスワップします。
 "_" により、関数呼び出し時の引数ラベルが省略可能であることを示しています。
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}
// 
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)

関数のタイプ

すべての関数には、パラメーターの型と関数の戻りの型で構成される特定の関数型があります。

例えば:

func addTwoInts(_ a: Int, _ b: Int) -> Int {
    return a + b
}
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
    return a * b
}
// この関数のタイプは (Int, Int) -> Int です。

func printHelloWorld() {
    print("hello, world")
}
// この関数のタイプは、() -> Void です。

関数タイプの使用

関数型は、Swiftの他の型と同じように使用します。たとえば、定数または変数を関数タイプとして定義し、適切な関数をその変数に割り当てることができます。

var mathFunction: (Int, Int) -> Int = addTwoInts

// 割り当てられた関数を次の名前で呼び出すことができます
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 5"

// 一致する型が同じである別の関数を、同じ変数に割り当てることができます。
mathFunction = multiplyTwoInts
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 6"

パラメータ型としての関数型

(Int, Int) -> Int などの関数型を、別の関数のパラメーターとして使用できます。

func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
    print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
// Prints "Result: 8"

戻り型としての関数型

関数の型を別の関数の戻り値の型として使用できます。これを行うに->は、戻り関数の戻り矢印の直後に完全な関数タイプを記述します。

func stepForward(_ input: Int) -> Int {
    return input + 1
}
func stepBackward(_ input: Int) -> Int {
    return input - 1
}

func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    return backward ? stepBackward : stepForward
}

var currentValue = 3
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)

print("Counting to zero:")
// Counting to zero:
while currentValue != 0 {
    print("\(currentValue)... ")
    currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// 3...
// 2...
// 1...
// zero!

ネストされた関数

この章でこれまでに出てきたすべての関数は、グローバルスコープで定義されたグローバル関数の例です。ネストされた関数と呼ばれる他の関数の本体内に関数を定義することもできます

ネストされた関数は、デフォルトでは外部から隠されていますが、それを囲んでいる関数から呼び出して使用することができます。囲んでいる関数は、ネストされた関数の1つを返すことにより、ネストされた関数を別のスコープで使用することもできます。

func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    func stepForward(input: Int) -> Int { return input + 1 }
    func stepBackward(input: Int) -> Int { return input - 1 }
    return backward ? stepBackward : stepForward
}
var currentValue = -4
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero now refers to the nested stepForward() function
while currentValue != 0 {
    print("\(currentValue)... ")
    currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// -4...
// -3...
// -2...
// -1...
// zero!