【Swift】KeyPath 式が生成する実際のオブジェクトについて

Swift4 で導入された KeyPath は、`init` ではなく、次のような `\<Type>.<path>` の形式でオブジェクトを生成します。

```swift
struct Foo {
    var bar: Int
}

let keyPath = \Foo.bar // WritableKeyPath<Foo, Int>
```

上記の場合、 `WritableKeyPath` に型推論されて、次のように読み書き可能です。

```swift
var foo = Foo(bar: 0)
print(foo[keyPath: keyPath]) // 0
foo[keyPath: keyPath] = 1
print(foo[keyPath: keyPath]) // 1
```

また、`var` ではなく、`let` で宣言した場合は、`KeyPath` クラスに型推論されます。

```swift
struct Foo {
    let bar: Int
}

let keyPath = \Foo.bar // KeyPath<Foo, Int>
```

このように、プロパティの宣言方法に応じて、下記のいずれかの型に型推論をするようです。

+ KeyPath<Root, Value>
+ WritableKeyPath
+ ReferenceWritableKeyPath<Root, Value>

どのように宣言すると、それぞれの型に推論されるのか？気になったので Playground で動作確認をしてみました。

### 環境
+ Xcode Version 10.2.1
+ Apple Swift version 5.0.1

## KeyPath<Root, Value>

読み込み専用のクラスである `KeyPath<Root, Value>` は、プロパティの宣言を次のようにした場合に推論されます。

+ `let` で宣言した定数
+ getter のみの計算プロパティ
+ `{ get }` 宣言したプロトコルのプロパティ

**`let` で宣言した定数**

```swift
struct Foo {
    let bar: Int = 0
}
```

**getter のみの計算プロパティ**

```swift
struct Foo {
    var bar: Int {
        return 0
    }
}
```

**`get` のみ宣言したプロトコルのプロパティ**

```swift
protocol Foo {
    var bar: Int { get }
}
```

このような場合、明示的に `WritableKeyPath` 型に代入しようとしてもコンパイルエラーになります。

```swift
let keyPath: WritableKeyPath<Foo, Int> = \Foo.bar // コンパイルエラー
```

また、 `WritableKeyPath` へのダイナミックキャストは実行時エラーになります。

```swift
let keyPath = \Foo.bar as! WritableKeyPath<Foo, Int> // 実行時エラー
```

実際に、次のようにして実行時の型を調べると `KeyPath` 型であることが分かります

```swift
let keyPath = \Foo.bar
type(of: keyPath) // KeyPath<Foo, Int>
```

## WritableKeyPath<Root, Value>

読み書き両用のクラスである `WritableKeyPath<Root, Value>` は、プロパティの宣言を次のようにした場合に推論されます。

+ `var` で宣言した変数
+ getter / setter 計算プロパティ
+ `{ get set }` 宣言したプロトコルのプロパティ

**`var` で宣言した変数**

```swift
struct Foo {
    var bar: Int = 0
}
```

**getter / setter 計算プロパティ**

```swift
struct Foo {
    var bar: Int {
        get { return 0 }
        set {}
    }
}
```

**`get` `set` 宣言したプロトコルのプロパティ**

```swift
protocol Foo {
    var bar: Int { get set }
}
```

このような場合、明示的に `WritableKeyPath` 型に代入することもできます。

```swift
let keyPath: WritableKeyPath<Foo, Int> = \Foo.bar // OK!
```

また、 `WritableKeyPath` のスーパークラスにキャストすることもできます。
次のように `KeyPath` として宣言することも可能です。

```swift
let keyPath: KeyPath<Foo, Int> = \Foo.bar // OK!
```

実行時の型を調べましたところ、型宣言の有無によらず `WritableKeyPath` でした。
次のように `KeyPath` として宣言しても、実際には `WritableKeyPath` 型のオブジェクトなので、
ダイナミックキャストも問題なくできます。

```swift
let keyPath: KeyPath<Foo, Int> = \Foo.bar
type(of: keyPath) // WritableKeyPath<Foo, Int>
keyPath as! WritableKeyPath<Foo, Int> // OK!
```

## ReferenceWritableKeyPath<Root, Value>

構造体などの値型で読み書き両用プロパティを宣言した場合は `WritableKeyPath` ですが、クラスで宣言した場合は `ReferenceWritableKeyPath` になります。

**`var` で宣言した変数**

```swift
class Foo {
    var bar: Int = 0
}
```

**getter / setter 計算プロパティ**

```swift
class Foo {
    var bar: Int {
        get { return 0 }
        set {}
    }
}
```

**`get` `set` 宣言したプロトコルのプロパティ**

```swift
protocol Foo: AnyObject {
    var bar: Int { get set }
}
```

プロトコルは `AnyObject` などでクラスに制限することで `ReferenceWritableKeyPath` になります。
`WritableKeyPath` の場合と同様に、型宣言した場合も、実際のオブジェクトは `ReferenceWritableKeyPath` になります。

```swift
let keyPath: KeyPath = \Foo.bar
type(of: keyPath) // ReferenceWritableKeyPath
```

## ここまでのまとめ

プロパティの宣言方法に応じて、下記のいずれかの型に型推論されます。

+ KeyPath<Root, Value>
+ WritableKeyPath
+ ReferenceWritableKeyPath<Root, Value>

`KeyPath` に推論される場合は、実行時のオブジェクトも `KeyPath` で、`WritableKeyPath` など書き込み可能なキーパスで型宣言しようとするとコンパイルエラーになります。

`WritableKeyPath` に推論される場合は、実行時のオブジェクトも `WritableKeyPath` で、`KeyPath` にアップキャスト可能で、またその後 `WritableKeyPath` にダウンキャストすることも可能です。

クラスの場合は、読み書き可能なプロパティは `ReferenceWritableKeyPath` になります。

## アクセス制御をした場合

Swift では、セッターのアクセスを制御して内部のみ公開することができます。

```swift
struct Foo {
    private(set) var bar: Int = 0 // getter は internal, setter は private
}
```

このような場合は、キーパスオブジェクトの生成場所によって、
型推論の挙動が変わりました。

+ setter にアクセスできる場合
+ getter のみアクセスできる場合

**setter にアクセスできる場合**
setter にアクセスできる場合は、`(Reference)WritableKeyPath` に型推論されます。

```swift
struct Foo {
    private(set) var bar: Int = 0
  
    func foo() {
        let keyPath = \Foo.bar // WritableKeyPath<Foo, Int>
    }
}
```

**getter のみアクセスできる場合**
getter のみアクセスできる場合は、`KeyPath` に型推論されます。

```swift
struct Foo {
    internal private(set) var bar: Int = 0
}

class FooUser {
    func use() {
        let keyPath = \Foo.bar // KeyPath<Foo, Int>
    }
}
```

明示的に `WritableKeyPath` 型を宣言してもコンパイルエラーになります。

```swift
class FooUser {
    func use() {
        let keyPath: WritableKeyPath<Foo, Int> = \Foo.bar // コンパイルエラー
    }
}
```

なのですが、実行時のオブジェクトは `WritableKeyPath` なので、ダイナミックキャストをしても実行時エラーになりません。

アクセス制御を無視して、次のように書き込みすることができます。

```swift
class FooUser {
    func use() {
        let keyPath = \Foo.bar as! WritableKeyPath<Foo, Int>
        var foo = Foo()
        print(foo.bar) // 0
        // foo.bar = 2 // コンパイルエラー
        foo[keyPath: keyPath] = 2 // WritableKeyPath 経由で書き込みができる
        print(foo.bar) // 2
    }
}
```

このようにアクセス制御を無視したコードを実際のプロダクトで書くことはないとは思いますが、内部で書き込みが可能なプロパティは、アクセスレベルによらず実行時のオブジェクトは `WritableKeyPath` になることが分かりました。

コンパイル時の検査は、アクセスする箇所に応じた KeyPath として推論してくれるので、
この仕組を利用することで Swift らしいコードが記述できそうです。