この投稿は何?
SwiftにおけるKeyPathについて、基本から解説します。
実行環境
- macOS 12.1
- Xcode 13.2.1
- Swift 5.5
KeyPathとは
要は、「あるデータ型に定義されたプロパティまでの参照(パス)」です。
Swift3時代のString KeyPath
KeyPathは、Objective-Cの時代から利用されていました。
Swiftでもそれを受け継いでおり、クラスに`objcMembers'属性をマークすることで利用できました。
@objcMembers class Kid: NSObject {
dynamic var nickname: String = ""
dynamic var age: Double = 0.0
dynamic var bestFriend: Kid? = nil
dynamic var friends: [Kid] = []
init(nickname: String, age: Double) {
self.nickname = nickname
self.age = age
}
}
// ベン君
let ben = Kid(nickname: "Benji", age: 5.5)
// Kid型のnicknameプロパティへのキーパスを作成
let kidsNameKeyPath = #keyPath(Kid.nickname) // String
// キーパスを使って取得した「ベン君のニックネーム」はAny型になってしまう
let name = ben.value(forKeyPath: kidsNameKeyPath) // value(forKeyPath: String) -> Any
print(name as! String) // Benji
この方法で取得したキーパスは、単純なString型として保持されます。
そして、String KeyPathを利用して取得したプロパティ値は、型情報が失われてしまう性質がありました。
Swiftに最適化されたKeyPath
Swift4では、全く新しい方法でKeyPathを利用できるようになりました。
struct BirthdayParty {
let celebrant: Kid
var theme: String
var attending: [Kid]
}
var bensParty = BirthdayParty(celebrant: ben, theme: "Construction", attending: [])
let birthdayKid = bensParty[keyPath: \BirthdayParty.celebrant]
bensParty[keyPath: \BirthdayParty.theme] = "Ninja"
let birthdayKidsAgeKeyPath = \BirthdayParty.celebrant.age // キーパスを作成
let birthdayboysAge = bensParty[keyPath: birthdayKidsAgeKeyPath] // 5.5
値型データのKeyPathでは型情報が維持され、よりシンプルに記述されます。

benはKid型インスタンスなので、基点型はKidであることが推論可能です。
従って、Kidを省略して以下のように記述できます。
let age = ben[KeyPath: \.age]
同様に、bensPartyはBirthdayParty型インスタンスなので、以下のコードも略記できます。
bensParty[keyPath: \BirthdayParty.theme] = "Ninja"
bensParty[keyPath: \.theme] = "Ninja" // 型推論による基点型の省略記法
この方法で取得したキーパスは、ReferenceWritableKeyPath型として保持されます。
そして、取得した値の型情報も維持されます。

クラスのキーパス
クラスでも同じようにして、キーパスを利用できます。
class C {
var p = ""
}
let c = C()
let cpKeyPaty = \C.p // ReferenceWritableKeyPath<C, p>
let cp = c[keyPath: \C.p] // String
![age = ben[keyPath Kid.age].png](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F70476%2Ff85ac4bc-5ed0-9b7c-638d-29ddeb5425c8.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=34c8cde9979292f0b0050401deb8dfff)