【Swift】Key-Path式

# この記事は何？

SwiftのKey-Path式について、[Appleの開発者向けドキュメント](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/expressions/#Key-Path-Expression)を独自に解説する。

> Swiftを基礎から学ぶには
自著、工学社より発売中の「[まるごと分かるSwiftプログラミング](https://amzn.asia/d/7YPWJwl)」をお勧めします。変数、関数、フロー制御構文、データ構造はもちろん、構造体からクロージャ、エクステンション、プロトコル、クロージャまでを基礎からわかりやすく解説しています。

# Key-Path式

キーパス式は、型のプロパティまたは添え字を参照する。
キー・値監視などの動的プログラミングタスクでは、キーパス式を使用する。
それらは、以下の形式で記述する。

*\\<#type name#>.<#path#>*

*type name* 部分は`String`、`[Int]`、`Set<Int>`などジェネリクスのパラメータを含む具体的な型の名前。

*path* 部分はプロパティ名、添え字、オプショナルチェーン式、および強制アンラップ式で構成される。
キー・パスを構成するこれらのコンポーネントは、任意の順序で、必要に応じて何度でも繰り返すことができる。

コンパイル時に、キー・パス式は[KeyPath](https://developer.apple.com/documentation/swift/keypath)クラスのインスタンスに置き換えられる。

キー・パスを使用して値にアクセスするには、あらゆる型で使用可能な`subscript(keyPath:)`添え字にキーパスを渡す。

```swift:キー・パスを使って、値を参照する
struct SomeStructure {
    var someValue: Int
}
let s = SomeStructure(someValue: 12)

// キー・パスのインスタンス
let pathToProperty = \SomeStructure.someValue
   
// 添え字にキー・パスを指定
let value = s[keyPath: pathToProperty] 
value    // 12
```

型が曖昧でも、コードの文脈から型推論が機能するなら、キー・パス式は型名を省略できる。
次のコードでは、`\SomeClass.someProperty`の型名を省略して`\.someProperty`と記述している。

```swift:
class SomeClass: NSObject {
    @objc dynamic var someProperty: Int
    init(someProperty: Int) {
        self.someProperty = someProperty
    }
}
let c = SomeClass(someProperty: 10)

// observe()メソッドのパラメータは「自身の型」なので
// キー・パス式の型名を省略できる
c.observe(\.someProperty) { object, change in
    // ...
}
```

パスは`self`を参照して、キー・パスIDの`(\.self)`を作成できる。
キー・パスIDはインスタンス全体を参照する。
そのため、キー・パスIDを使用して、1つのステップで変数に保存されているすべてのデータにアクセスして変更できる。

```swift:
var compoundValue = (a: 1, b: 2)    // ラベル付きタプル

compoundValue[keyPath: \.self] = (a: 10, b: 20)
compoundValue    // (a: 10, b: 20)
```

パスに、いくつかのプロパティ名をピリオド`.`で連結して記述すると、「プロパティの値になっているプロパティ」を参照できる。
例えば、キー・パス式 `\OuterStructure.outer.someValue`は、`OuterStructure`型にある`outer`プ
ロパティの`someValue` プロパティにアクセスする。

```swift:プロパティをドリルダウンするキー・パス式
struct OuterStructure {
    var outer: SomeStructure
    init(someValue: Int) {
        self.outer = SomeStructure(someValue: someValue)
    }
}
let nested = OuterStructure(someValue: 24)
nested.outer    // SomeStructureのインスタンス

let nestedKeyPath = \OuterStructure.outer.someValue
let nestedValue = nested[keyPath: nestedKeyPath]
// nestedValue is 24
```

添え字に指定するパラメータが`Hashable`プロトコルに準拠している限り、パスには、括弧`[]`を使用して添え字を含めることができる。
例えば、キー・パスの添え字を使用して、配列の2番目の要素にアクセスする。

```swift:
// 配列はHashable
let greetings = ["hello", "hola", "bonjour", "안녕"]

let myGreeting = greetings[keyPath: \[String].[1]]
// myGreeting is 'hola'
```

添え字の値には、名前付き値またはリテラルを指定できる。
値は、値セマンティクスを使用してキーパスにキャプチャされる。
次のコードは、キー・パス式とクロージャの両方で変数`index`を使用して、`greetings`配列の3番目の要素にアクセスする。
`index`が変更されると、キー・パス式は3番目の要素を参照するが、クロージャは新しいインデックスを使用する。

```swift:
var index = 2

// 「文字列を要素とする配列」の3つ目の要素を参照するキー・パス
let path = \[String].[index]

// 「文字列の配列を受け取って、3つ目の文字列を返すクロージャ式
let fn: ([String]) -> String = { 
    strings in strings[index]    // クロージャ内でindexを使用
}

// どちらも同じく、3つ目の要素を参照
greetings[keyPath: path]    // "bonjour"
fn(greetings)               // "bonjour"

index += 1   // indexを更新

// 'index'を更新しても、'path'には影響しない
// 一方、'fn'クロージャでの'index'は更新した後の値を使う
greetings[keyPath: path]    // "bonjour"
fn(greetings)               // "안녕"

```

パスでは、オプショナルチェーンと強制アンラップを使用できる。
次のコードは、キー・パス式のオプションのチェーンを使用して、オプショナルな文字列プロパティにアクセスする。

```swift:オプショナルチェーンのキー・パス
// greetings配列の先頭"hello"は5文字
let firstGreeting: String? = greetings.first
firstGreeting?.count as Any    // "Optional(5)"

// 上と同じことをする
let count = greetings[keyPath: \[String].first?.count]
count as Any    // "Optional(5)"
```

キー・パスのコンポーネントを組み合わせて、型で深くネストされた値にアクセスできる。
次のコードは、コンポーネントを組み合わせたキーパス式を使用して、配列の辞書のさまざまな値とプロパティにアクセスする。

```swift:
// 「要素が辞書」の配列
let interestingNumbers = ["prime": [2, 3, 5, 7, 11, 13, 17],
                          "triangular": [1, 3, 6, 10, 15, 21, 28],
                          "hexagonal": [1, 6, 15, 28, 45, 66, 91]]
interestingNumbers[keyPath: \[String: [Int]].["prime"]] as Any        // "Optional([2, 3, 5, 7, 11, 13, 17])"
interestingNumbers[keyPath: \[String: [Int]].["prime"]![0]]           // "2"
interestingNumbers[keyPath: \[String: [Int]].["hexagonal"]!.count]    // "7"
interestingNumbers[keyPath: \[String: [Int]].["hexagonal"]!.count.bitWidth] // "64"
```

通常は関数かクロージャを提供するコンテキストで、キー・パス式を使用できる。
具体的には、ルートが`SomeType`型で、パスが`(SomeType) -> Value`型の関数またはクロージャの代わりに、`Value`型の値を生成するキー・パス式を使用できる。

```swift:
struct Task {
    var description: String
    var completed: Bool
}
var toDoList = [
    Task(description: "Practice ping-pong.", completed: false),
    Task(description: "Buy a pirate costume.", completed: true),
    Task(description: "Visit Boston in the Fall.", completed: false),
]

// Both approaches below are equivalent.
let descriptions = toDoList.filter(\.completed).map(\.description)
let descriptions2 = toDoList.filter { $0.completed }.map { $0.description }
```

キー・パス式の副作用は、式が評価される時点でのみ評価される。
たとえば、キーパス式の添え字内で関数呼び出すと、キー・パスが使用されるたびにではなく、式の評価の一部として関数が1回だけ呼び出される。

```swift:
func makeIndex() -> Int {
    print("Made an index")
    return 0
}
// The line below calls makeIndex().
let taskKeyPath = \[Task][makeIndex()]
// Prints "Made an index"

// Using taskKeyPath doesn't call makeIndex() again.
let someTask = toDoList[keyPath: taskKeyPath]
```

Objective-C APIと対話するコードでキーパスを使用する方法の詳細については、[SwiftでのObjective-Cランタイム機能の使用]()を参照。
キー値コーディングとキー値監視の詳細については、[キー値コーディングプログラミングガイド]()および[キー値監視プログラミングガイド]()を参照。

> key-path式の文法
キー・パス式 → \\ *type?* . *key-path-components*
キー・パスの構成 → *key-path-component* | *key-path-component* . *key-path-components*
キー・パスの各部品 → *identifier* *key-path-postfixes?* | *key-path-postfixes*
キー・パスの接尾辞 → *key-path-postfix* *key-path-postfixes?*
キー・パスの各接尾辞 → ? | ! | self | [ *function-call-argument-list* ]