#keyPathとKeyPath

## \#keyPath - Key-Path String Expression
Swift 3で `#keyPath` が追加され、KVCなどのキー指定をコンパイル時にチェックできるようになりました。
これを使うことでキー値を文字列で指定することによるtypoを防ぎ、リファクタリングにも追従できるようになります。

* Swift 3

  ```swift
  class Person: NSObject {
      dynamic var name: String
      
      init(name: String) {
          self.name = name
      }
  }
  
  let person = Person(name: "hoge")
    
  #keyPath(Person.name)  // "name"
  
  // KVC
  person.setValue("fuga", forKey: #keyPath(Person.name))
  person.value(forKey: #keyPath(Person.name))  // "fuga"
  ```

* Swift 2以前

  ```swift
  person.setValue("fuga", forKey: "name")
  person.valueForKey("name")  // "fuga"
  ```

`#keyPath` では何かしら型が用意されているわけではなく、コンパイル後は文字列リテラルに置き換えられます。
そのためSwift 3以降でもSwift 2以前と同じく文字列でキーの指定ができます。

* Swift 3

  ```swift
  // 今まで通り文字列でキー値の指定もできる。結果は#keyPathを使った場合と同じ。
  person.value(forKey: "name") // fuga
  ```

### \#keyPathの問題点

`#keyPath`によってtypoなどの問題は防げるようになったのですが、
その一方でKVCやKVOにおける値の操作は`Any`を通して行うままなので片手落ちな感じです。

例えばKVCで`String`型の値を渡すべきところに`Int`型の値を渡してもコンパイラは何も感知しません。

```swift
type(of: person.value(forKey: #keyPath(Person.name))) // Optional<Any>.Type

person.setValue(0, forKey: #keyPath(Person.name)) // 実行時エラー
```

[SE-0161](https://github.com/apple/swift-evolution/blob/master/proposals/0161-key-paths.md)で挙げられている問題もまとめると`#keyPath`には以下のような問題があります。

* 型情報の喪失
* 無駄に構文解析が遅くなる
* NSObjectにしか使えない
* Darwinプラットフォームのみ対応

## KeyPath - Key-Path Expression

`#keyPath`の問題点を解決するため、Swift 4で `KeyPath` が追加されました。

* Swift 4

  ```swift
  class Person {
      var name: String
      var age: Int = 0

      init(name: String) {
          self.name = name
      }
  }

  let person = Person(name: "hoge")

  person[keyPath: \Person.name] = "fuga"

  person[keyPath: \Person.name] // "fuga"

  ```

型情報が維持されているため`#keyPath`で片手落ちだった部分が解消されています。

```swift
type(of: person[keyPath: \Person.name]) // String.Type
person[keyPath: \Person.name] = 0 // コンパイルエラー
```

### KeyPathを使ったKVO

`KeyPath`の追加に合わせてKVOも新しい方法が追加されています。
こちらはObjective-Cランタイムに依存しているので、
利用できるのはNSObjectを継承しているクラスの`@objc dynamic var`なメンバのみです。

例えば`WKWebView`の進捗`estimatedProgress`を監視するコードは下記のようになります。

```swift
import UIKit
import WebKit

class ViewController: UIViewController {
    let webView = WKWebView()

    var observation: NSKeyValueObservation?

    override func viewDidLoad() {
        super.viewDidLoad()

        observation = webView.observe(\.estimatedProgress, options: .new) { webView, change in
            guard let progress = change.newValue else {
                return
            }
            print(progress)
        }

        webView.load(URLRequest(url: URL(string: "https://google.com")!))
    }
}

```

Swift 3以前は　`addObserver` で監視対象を登録して変更は全て`observeValue` で受け取る形でしたが、
処理をクロージャで書けるようになって随分使いやすくなりました。
もちろんクロージャに渡される通知元と監視している値の両方とも型が消されていないので安心して使用できます。

## KeyPathの実装を少しだけ見てみる

せっかくなので実装についても少しだけ見てみます。

`\Person.name`の型を調べてみると`ReferenceWritableKeyPath<Person, String>`となっています。
これを辿っていくとざっくり下記のような継承関係になっていることがわかります。
(※辿らなくても[SE-0161](https://github.com/apple/swift-evolution/blob/master/proposals/0161-key-paths.md)に書いてあります)

```swift
AnyKeyPath
    ↑
PartialKeyPath<Root>
    ↑
KeyPath<Root, Value>
    ↑
WritableKeyPath<Root, Value>
    ↑
ReferenceWritableKeyPath<Root, Value>
```

`AnyKeyPath`と`PartialKeyPath<Root>`は型安全でないObjective-C APIとの繋ぎのためにあるようです。

`PartialKeyPath<Root>`があるのでキーをまとめることができます。

```swift
let keys = [
    \Person.name,
    \Person.age
]


let values = keys.map { person[keyPath: $0] } //　ただし [Any] になる
```

KeyPathの実用的な事例としてはkishikawakatsumiさんの[kishikawakatsumi/Kuery](https://github.com/kishikawakatsumi/Kuery)があります。

## 参考
[SE-0062](https://github.com/apple/swift-evolution/blob/master/proposals/0062-objc-keypaths.md) Referencing Objective-C key-paths
[SE-0161](https://github.com/apple/swift-evolution/blob/master/proposals/0161-key-paths.md) Smart KeyPaths: Better Key-Value Coding for Swift
[kishikawakatsumi/Kuery](https://github.com/kishikawakatsumi/Kuery)