KeyPath は、Swiftにおけるオブジェクトのプロパティへの型付きパスであり、ファーストクラスの値として表現されます。KVC(Key-Value Coding)の文字列キーとは異なり、KeyPathはコンパイル時にコンパイラによってチェックされます。コンパイラはルートオブジェクトの型、プロパティの型を認識し、パスが存在することを保証できます。KeyPathはCombineでのリアクティブバインディング、SwiftUIでの双方向バインディング、標準ライブラリでのコレクションのソートやフィルタリングに使用されます。Apple Developer、2025によると、KeyPathはSwiftにおける関数型プログラミングの基本的な構成要素です。
重要なポイント
KeyPath は、Swift標準ライブラリの型で、オブジェクトのプロパティへのパスを表します。ドット記法(object.property)や文字列キー(value(forKey:))で手動でプロパティにアクセスするのとは異なり、KeyPathはファーストクラスのオブジェクトです:引数として渡したり、変数に格納したり、合成したりできます。
KeyPathはバックスラッシュとプロパティへのパスで記述します:\Person.name。パスはネスト可能です:\Person.address.city。コンパイラはPersonにnameプロパティがあり、nameが実際にStringであることを検証します。プロパティが存在しないか型が間違っている場合、コードはコンパイルされません。
struct Person: Codable {
let name: String
let age: Int
var isActive: Bool
}
// KeyPath(読み取り専用)
let namePath: KeyPath<Person, String> = \Person.name
let person = Person(name: "Alice", age: 30, isActive: true)
let name = person[keyPath: namePath]
KeyPathを介したプロパティアクセスは、サブスクリプト object[keyPath: path] を使用して行われます。読み取り専用プロパティにはKeyPathが使用されます。プロパティが可変(var)の場合、WritableKeyPathが利用可能です。参照型の場合、ReferenceWritableKeyPathを使用すると、KeyPathを介して直接プロパティを変更できます。
KeyPath には、プロパティへのアクセスレベルを反映したサブタイプ階層があります。基本階層は次のとおりです:AnyKeyPath → PartialKeyPath → KeyPath → WritableKeyPath → ReferenceWritableKeyPath。各サブタイプは機能を追加します。
KeyPath — 読み取り専用。WritableKeyPath — 値型(struct)の読み取りと書き込み。ReferenceWritableKeyPath — 参照型(class)の読み取りと書き込み。Swiftはコンテキストに基づいて自動的に適切なサブタイプを選択します。
| 型 | アクセス | オブジェクト型 | 変更 |
|---|---|---|---|
| KeyPath | 読み取り専用 | 任意 | 不可 |
| WritableKeyPath | 読み書き可能 | 値型(inout) | mutatingコンテキスト経由 |
| ReferenceWritableKeyPath | 読み書き可能 | 参照型 | 直接代入 |
// 値型用のWritableKeyPath
var mutablePerson = Person(name: "Bob", age: 25, isActive: false)
let writablePath: WritableKeyPath<Person, Bool> = \Person.isActive
mutablePerson[keyPath: writablePath] = true
// クラス用のReferenceWritableKeyPath
class User: NSObject {
@objc dynamic var name: String = ""
}
let user = User()
let refPath: ReferenceWritableKeyPath<User, String> = \User.name
user[keyPath: refPath] = "Charlie"
重要な違い:値型の場合、KeyPathを介した設定には var(inoutコンテキスト)が必要ですが、参照型の場合は可変プロパティのみが必要です。これはSwiftの一般的なセマンティクスと一致しています。値型は値渡しされるため、変更にはコンテナの可変性が必要です。
KeyPath はCombineとSwiftUIで中心的な役割を果たします。Combineでは、assign演算子がKeyPathを使用してパブリッシャーの値をオブジェクトのプロパティにバインドします。SwiftUIでは、Bindingがモデルとビュー間の双方向リンクにKeyPathを使用します。
assign(to:on:) 演算子はReferenceWritableKeyPathとオブジェクトを受け入れます。パブリッシャーが値を発行すると、指定されたプロパティに自動的に書き込まれます。これは手動代入なしで状態を管理する宣言的な方法です。
import Combine
class SettingsViewModel: ObservableObject {
@Published var volume: Float = 0.5
var cancellables = Set<AnyCancellable>()
func bindSlider(publisher: AnyPublisher<Float, Never>) {
publisher
.assign(to: \SettingsViewModel.volume, on: self)
.store(in: &cancellables)
}
}
SwiftUIでは、@Bindingが親ビューと子ビューを接続するためにKeyPathを使用します。親は Binding(get:set:) またはプロジェクション($)を介して作成されたBinding
KeyPath を使用すると、柔軟で型安全なソートおよびフィルタリング関数を作成できます。毎回クロージャを渡す代わりに、コレクションをソートするプロパティへのKeyPathを渡すことができます。これによりコードがよりクリーンになり、重複が減少します。
関数 sorted
extension Sequence {
func sorted<Value: Comparable>(
by keyPath: KeyPath<Element, Value>,
ascending: Bool = true
) -> [Element] {
ascending
? self.sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] }
: self.sorted { $0[keyPath: keyPath] > $1[keyPath: keyPath] }
}
}
let sortedByName = people.sorted(by: \Person.name)
let sortedByAgeDesc = people.sorted(by: \Person.age, ascending: false)
この実装は、クロージャを記述せずに任意のComparableプロパティでソートします。姓でソートするには、KeyPathを変更するだけです:\Person.lastName。カスタムキー(例:名前の長さ)でソートする必要がある場合は、内部でマップが使用されます — 開発者は非標準のケースのために独自のクロージャを作成できます。
AnyKeyPath はKeyPath階層内の基本型で、特定のルート型と値型に関する情報を消去します。AnyKeyPathはどのオブジェクト型で呼び出されているか、どの値型を返すかを認識しません。これは異種KeyPathをコレクションに格納したり、リフレクションに役立ちます。
AnyKeyPathは、異なる型の異なるプロパティへのパスのリストを格納する必要がある場合に使用されます。例えば、UIコンフィギュレータでは、各パスが特定のプロパティを設定し、プロパティの型が異なる場合があります。AnyKeyPathを使用すると、すべてのパスを統一的に扱えます。
protocol Configurable {
func apply(_ keyPath: any PartialKeyPath<Self>, value: any)
}
extension Configurable {
func configure(_ pairs: (any KeyPath<Self, any>, any)...) {
for (path, value) in pairs {
apply(path, value: value)
}
}
}
extension Person: Configurable { }
// AnyKeyPathを介したプロパティ設定を許可
AnyKeyPathは値を取得する際に 型キャスト が必要です。特定の型が消去されているためです。完全な型安全性のためには、型付きのKeyPath
KeyPath はKVCの文字列キー(value(forKey:))よりも大幅に効率的です。文字列キーはObjective-Cのランタイムメカニズムを通じて解決され、文字列の解析、ランタイムルックアップ、型キャストが含まれます。KeyPathはネイティブのSwift構造に基づく静的なメカニズムです。
ベンチマークによると、KeyPathはプロパティアクセスにおいてvalue(forKey:)よりも平均で 10~20倍 高速です。この差は、ランタイムルックアップと動的ディスパッチがないことに起因します。KeyPathは、コンパイラの最適化が十分であれば、メモリ内のプロパティオフセットに直接アクセスします。
func readWithKeyPath(person: Person) -> String {
person[keyPath: \Person.name]
}
func readWithKVC(person: Person) -> String {
person.value(forKey: "name") as! String
}
KVCは遅いだけでなく、型安全でない という問題もあります。文字列 “name” が存在しない可能性があり、as! String キャストがクラッシュする可能性があります。KeyPathはコンパイル時にプロパティの存在と型の正確性を保証します。パフォーマンスが重要な部分(アニメーション、数千項目のリスト)では、KeyPathが唯一の正しい選択肢です。
よくある質問
KeyPath は、通常の値として渡すことができるオブジェクトのプロパティへのポインタです。person.nameと書く代わりに、\Person.nameというパスを作成し、任意のPersonインスタンスのプロパティを読み書きするために使用します。
KeyPath — 読み取り専用。WritableKeyPath — inoutコンテキストを通じて値型(struct)の読み取りと書き込み用。ReferenceWritableKeyPath — 追加条件なしで参照型(class)の読み取りと書き込み用。
KeyPathは Combine(assign(to:on:))、SwiftUI(Binding、FocusState)、コレクションのソート(sorted(by:))、Core Data(NSSortDescriptor)、ネストされたプロパティへの安全なアクセスで広く使用されています。
はい、KeyPathは完全に型安全です。パスのエラー(タイプミス、型の誤り)は実行時ではなくコンパイル時に検出されます。value(forKey:)は文字列を使用し、キーが存在しない場合にNSUndefinedKeyExceptionでクラッシュする可能性があります。
直接はできません — Swiftは文字列からの動的なKeyPath作成をサポートしていません。ただし、AnyKeyPath を使用して静的に取得したKeyPathを格納および渡すことができます。文字列による動的プロパティアクセスには、Objective-Cランタイムでvalue(forKey:)を使用してください。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。