Swift4のKeyPathを学ぶ

## はじめに

KeyPathはSwift4から導入されました。
同じくSwift4から導入されたKVOのコードとともに見ることもあり、これは何だ？思った記憶があります。その時はKVOのクロージャ採用【[Qiita記事参照](https://qiita.com/BlueEventHorizon/items/bf37428b54b937728dc7)】に気を取られていてあまり深く考えなかったのですが、今回 try! Swift 2019でKeyPathの発表[^try!Swift]があったため、改めて資料を参考にしながらXcodeで動かしてみました。

## 環境

Xcode10.1
Swift4.2

## KeyPathの定義

KeyPathの公式ドキュメントはこちらです。
https://developer.apple.com/documentation/swift/swift_standard_library/key-path_expressions
https://developer.apple.com/documentation/swift/keypath

これによるとKeyPathには、いくつかの種類があります。

| Class           |       Declaration |  |
|:-----------------|:------------------|:----|
| KeyPath         | `class KeyPath<Root, Value> : PartialKeyPath<Root>`|（リードオンリー）|
| PartialKeyPath  | `class PartialKeyPath<Root> : AnyKeyPath` |（リードオンリー）KeyPath のsuper class|
| AnyKeyPath      | `class AnyKeyPath` |（リードオンリー）PartialKeyPath のsuper class|
| WritableKeyPath  | `class WritableKeyPath<Root, Value> : KeyPath<Root, Value>` | 値の読み書きをサポートするキーパス。|
|ReferenceWritableKeyPath| `class ReferenceWritableKeyPath<Root, Value> : WritableKeyPath<Root, Value>` |参照セマンティクスを使用して値の読み書きをサポートするキーパス。|

下記の（１）ようにKeyPathを記述するとUserのnameにアクセスできます。

```swift:Swift
    func testKeyPathBasic2()
    {
        struct User {
            var name: String
        }
        var player = User(name: "Lupin")
        let namePath = \User.name // <--- （１）
        print(String(describing: type(of: namePath)))  // WritableKeyPath<User, String>
        player[keyPath: namePath] = "Goemon" // <--- （２）
        
        XCTAssertTrue(player.name == "Goemon")
    }
```

ここで（１）のnamePathの型を見ると `KeyPath<User, String>` ではなく、`WritableKeyPath<User, String>` であることが分かります。
KeyPathはリードオンリーのため、WritableKeyPathにしないと（２）でエラーになるからです。

## KeyPath Composition

KeyPathは下記のようにパスを構成することができます。これによって複雑な構造体の変数にアクセスすることもできます。

```swift:Swift
    func testKeyPathBasic3()
    {
        struct User {
            var name: Name
        }
        struct Name {
            var first: String
            var last: String
        }
        
        let player = User(name: Name(first: "Lupin", last: "the 3rd" ))
        let namePath = \User.name
        let firstPath = \Name.first
        let firstNamePath = namePath.appending(path: firstPath) // ここ
        
        let firstName = player[keyPath: firstNamePath]
        
        XCTAssertTrue(firstName == "Lupin")
    }
```

上記のパスの連結はそもそも下記のように書けます。

```swift:Swift
        let firstNamePath2 = \User.name.first  // パスを記述する
        let firstName2 = player[keyPath: firstNamePath2]
```

## KeyPathの応用

try! Swift 2019で取り上げられたKeyPathの手法に関しては正直メリットがそれほど伝わってきませんでした。（英語理解力不足もありますが）
しかしながら下記のようなKeyPathを使った拡張は有効だと思います。

```swift:Swift
enum SortOrder
{
    case ascending
    case descending
}

extension Sequence
{
    func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>, order: SortOrder = .ascending) -> [Element] {
        return sorted { a, b in
            switch order
            {
            case .ascending:
                return a[keyPath: keyPath] < b[keyPath: keyPath]
            case .descending:
                return a[keyPath: keyPath] > b[keyPath: keyPath]
            }
        }
    }
}
```

このsortedメソッドは、Comparableな型の変数のKeyPathでソートします。

```swift:Swift
        struct User {
            var name: Name
        }
        struct Name {
            var first: String
            var last: String
        }

        let person1 = User(name: Name(first: "Lupin", last: "the 3rd" ))
        let person2 = User(name: Name(first: "Goemon", last: "the 13th" ))
```

User、Nameの変数でソートする場合は、通常は下記のようにsort関数を用います。

```swift:Swift
        var people = [person1, person2]
        let newPeople = people.sort{ $0.name.first > $1.name.first }
```

KeyPathを使用した場合は下記のようになります。

```swift:Swift
        let people = [person1, person2]
        let newPeople = people.sorted(by: \User.name.first)
```

KeyPathを用いると
１）対象になる配列はletで良い
２）sorted()という関数になっている
３）上記関数の引数がKeyPathでより内容が明快
という違いがあります。

軽微な違いではありますが、より可読性が高いコードとなっていると思います。

## 参考

このような拡張を行うライブラリーとして[KeyPathKit](https://github.com/vincent-pradeilles/KeyPathKit)があります。
まだ使用してなくて感想は書けませんが、ソースコードを読む限りでは同じような方向を向いた使いやすいライブラリーのようです。

[^try!Swift]: try! Swift 2019で取り上げられた「[Keypath入門](https://www.tryswift.co/events/2019/tokyo/jp/#keypath)」