Subscript, Keypathでプロパティ参照の可読性アップ (Swift)

Swiftでインスタンスのプロパティにアクセスする際の方法は、`.プロパティ名`がよく使われるかと思いますが、それ以外にも実はいくつか方法があります。
ここではそれらの方法のうち、覚えておくと可読性アップにつながるsubscriptとkeypathについて使い方、メリットを合わせて紹介していきます。

## Subscript
Swift(に限らずですが)でDictionaryのプロパティにアクセスする際、以下のような書き方がなされると思います。
```swift
let foodPrices: [String: Int] = ["curry": 1000, "hamburger": 800, "pasta": 850]
print(foodPrices["curry"]) // 1000
```
実はこちらのような書き方を、インスタンスにおいてもすることができます。
それを行うのに必要なのが、`subscript`です。
subscriptは以下に示す通り、computed propertyのように定義できます。`setter`を定義すれば、辞書型のようにプロパティの書き換えも可能です。
```swift
class Fruit {
    let id = UUID().uuidString
    let weight: Int
    var name: String
    var detail: [String: String]
    let producer: Producer

    init(weight: Int, 
         name: String,
         detail: [String : String],
         producer: Producer) {
        self.weight = weight
        self.name = name
        self.detail = detail
        self.producer = producer
    }

    // computed propertyのようにgetter, setterを定義できる。
    subscript(key: String) -> String {
        get {
            switch key {
                case "weight": 
                    return "\(weight)"
                case "name": 
                    return name
                default: 
                    return detail[key] ?? ""
            }
        }
        set(newValue) {
            switch key {
                case "name":
                    name = newValue
                default:
                    detail[key] = newValue
            }
        }
    }
}

class Producer {
    var name: String
    let age: Int

    init(name: String, 
         age: Int) {
        self.name = name
        self.age = age
    }
}

var apple = Fruit(weight: 100,
                  name: "Apple",
                  detail: ["prefecture": "Aomori",
                           "color": "green"],
                  producer: Producer(name: "aaa",
                                     age: 60))

// 辞書型のように["プロパティ名"]で値へのアクセスができる。
print(apple["name"]) // Apple
print(apple["color"]) // green
apple["color"] = "red"
print(apple["color"]) // red
```

### `.プロパティ名`でアクセスする
また、`dynamicMemberLookup`と呼ばれる修飾子を用いると、通常のプロパティのようなアクセスが可能になります。
```swift
// 追加
@dynamicMemberLookup
class Fruit {
    ...
    // 追加
    subscript(dynamicMember key: String) -> String {
        return detail[key] ?? ""
    }
}

print(apple.prefecture) // Aomori
```
通常のプロパティのように書くとネストが深くなりすぎるが、頻繁に使用する値などにはこちらの方法が有効かと思います。

## KeyPath
また同様にプロパティにアクセスしやすくする方法として、`KeyPath`と呼ばれる文法が存在します。
こちらは、以下のように記述することで、プロパティへの参照を保持できる方法です。
```swift
let age = \Fruit.producer.age
print(apple[keyPath: age]) // 60
```
subscript同様、値の更新も行うことができます。
```swift
let name = \Fruit.producer.name
print(apple[keyPath: name]) // aaa
apple[keyPath: name] = "fuga"
print(apple[keyPath: name]) // fuga
```
また、既存のKeyPathに新たなPathをappendすることができます。
```swift
let producerPath = \Fruit.producer
let namePath = producerPath.appending(path: \.name)
print(apple[keyPath: namePath]) // aaa
```
### `.プロパティ名`でアクセスする
KeyPathの場合も、以下のような形で`dynamicMemberLookup`を用いることができます。
```swift
struct Price {
    let yen, dollar: Int
}

@dynamicMemberLookup
class Fruit {
    let id = UUID().uuidString
    let weight: Int
    var name: String
    var detail: [String: String]
    let producer: Producer
    let price: Price // 追加

    init(weight: Int, 
         name: String,
         detail: [String : String],
         producer: Producer,
         price: Price) {
        self.weight = weight
        self.name = name
        self.detail = detail
        self.producer = producer
        self.price = price // 追加
    }
    ...
    // 以下を追加。KeyPath<対象クラス名, KeyPath対象の型>の形で定義する。
    subscript<T>(dynamicMember keyPath: KeyPath<Price, T>) -> String {
        return "\(price[keyPath: keyPath])"
    }
}

var apple = Fruit(weight: 100,
                  name: "Apple",
                  detail: ["prefecture": "Aomori",
                           "color": "green"],
                  producer: Producer(name: "aaa",
                                     age: 60), 
                  price: Price(yen: 150, // 追加
                               dollar: 1))

print(apple.yen) // 150
```
subscriptとの使い分けが難しいところですが、KeyPathの大きな特徴は参照の保持ができることです。
Pathにappendしていくことができる特徴を活かし、既存の参照からネストの深いところまで次々に処理をしていく場合などに重宝するのではないか、と考えます。

## まとめ
- subscriptはクラスや構造体で、プロパティに辞書型のような`["プロパティ名"]`の書き方で値にアクセスするのに使われる。
- KeyPathは値への参照を保持しながら、任意のプロパティにアクセスするのに使われる。
  - 既存の参照にさらに深いネストへの参照をappendしていくことができる。
- dynamic member lookupを使うと、`.プロパティ名`でプロパティへのアクセスをすることができる。
  - さらに存在しないプロパティへのアクセスはコンパイルエラーの代わりにdefault値を返すので、型安全に`.`記法を使うことができる。

### 最後に
こちらは私が書籍で学んだ学習内容をアウトプットしたものです。
わかりにくい点、間違っている点等ございましたら是非ご指摘お願いいたします。