KeyPath: 主要な概念と型付きアクセス

著者: IT Sectr 公開日: 2026-06-18 読了時間: 10 分

KeyPath は、Swiftにおけるオブジェクトのプロパティへの型付きパスであり、ファーストクラスの値として表現されます。KVC(Key-Value Coding)の文字列キーとは異なり、KeyPathはコンパイル時にコンパイラによってチェックされます。コンパイラはルートオブジェクトの型、プロパティの型を認識し、パスが存在することを保証できます。KeyPathはCombineでのリアクティブバインディング、SwiftUIでの双方向バインディング、標準ライブラリでのコレクションのソートやフィルタリングに使用されます。Apple Developer、2025によると、KeyPathはSwiftにおける関数型プログラミングの基本的な構成要素です。

重要なポイント

  • KeyPath — プロパティへの型付きパス、コンパイラによって検証済み
  • WritableKeyPath — 書き込みアクセス可能なプロパティのためのKeyPathのサブタイプ
  • ReferenceWritableKeyPath — 参照型の可変プロパティ用
  • Combine はKeyPathを使用してパブリッシャーをオブジェクトのプロパティにバインド
  • AnyKeyPath — コレクションにKeyPathを格納するための型消去バリアント

SwiftにおけるKeyPathとは?

KeyPath は、Swift標準ライブラリの型で、オブジェクトのプロパティへのパスを表します。ドット記法(object.property)や文字列キー(value(forKey:))で手動でプロパティにアクセスするのとは異なり、KeyPathはファーストクラスのオブジェクトです:引数として渡したり、変数に格納したり、合成したりできます。

KeyPathの構文

KeyPathはバックスラッシュとプロパティへのパスで記述します:\Person.name。パスはネスト可能です:\Person.address.city。コンパイラはPersonにnameプロパティがあり、nameが実際にStringであることを検証します。プロパティが存在しないか型が間違っている場合、コードはコンパイルされません。

swift
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の階層: WritableKeyPathとReferenceWritableKeyPath

KeyPath には、プロパティへのアクセスレベルを反映したサブタイプ階層があります。基本階層は次のとおりです:AnyKeyPath → PartialKeyPath → KeyPath → WritableKeyPath → ReferenceWritableKeyPath。各サブタイプは機能を追加します。

KeyPathのアクセスレベル

KeyPath — 読み取り専用。WritableKeyPath — 値型(struct)の読み取りと書き込み。ReferenceWritableKeyPath — 参照型(class)の読み取りと書き込み。Swiftはコンテキストに基づいて自動的に適切なサブタイプを選択します。

アクセスオブジェクト型変更
KeyPath読み取り専用任意不可
WritableKeyPath読み書き可能値型(inout)mutatingコンテキスト経由
ReferenceWritableKeyPath読み書き可能参照型直接代入
swift
// 値型用の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の一般的なセマンティクスと一致しています。値型は値渡しされるため、変更にはコンテナの可変性が必要です。

CombineとSwiftUIにおけるKeyPath

KeyPath はCombineとSwiftUIで中心的な役割を果たします。Combineでは、assign演算子がKeyPathを使用してパブリッシャーの値をオブジェクトのプロパティにバインドします。SwiftUIでは、Bindingがモデルとビュー間の双方向リンクにKeyPathを使用します。

CombineのAssign

assign(to:on:) 演算子はReferenceWritableKeyPathとオブジェクトを受け入れます。パブリッシャーが値を発行すると、指定されたプロパティに自動的に書き込まれます。これは手動代入なしで状態を管理する宣言的な方法です。

swift
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により、SwiftUIは自動的に変更を追跡し、余計な手間なくUIを更新できます。

ソートとフィルタリングのためのKeyPathの使用

KeyPath を使用すると、柔軟で型安全なソートおよびフィルタリング関数を作成できます。毎回クロージャを渡す代わりに、コレクションをソートするプロパティへのKeyPathを渡すことができます。これによりコードがよりクリーンになり、重複が減少します。

KeyPathによるジェネリックソート関数

関数 sorted は KeyPath を受け入れます(Value: Comparable)。これにより、ソートプロパティが 比較 をサポートすることが保証されます。コンパイラはコンパイル時にこれをチェックします。

swift
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

AnyKeyPath はKeyPath階層内の基本型で、特定のルート型と値型に関する情報を消去します。AnyKeyPathはどのオブジェクト型で呼び出されているか、どの値型を返すかを認識しません。これは異種KeyPathをコレクションに格納したり、リフレクションに役立ちます。

AnyKeyPathの使用

AnyKeyPathは、異なる型の異なるプロパティへのパスのリストを格納する必要がある場合に使用されます。例えば、UIコンフィギュレータでは、各パスが特定のプロパティを設定し、プロパティの型が異なる場合があります。AnyKeyPathを使用すると、すべてのパスを統一的に扱えます。

swift
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が常に優先されます。AnyKeyPathは、シリアライゼーションやリフレクションなど、コンパイル時に型が実際に不明な場合のためのツールです。

KeyPathのパフォーマンス vs 文字列キー

KeyPath はKVCの文字列キー(value(forKey:))よりも大幅に効率的です。文字列キーはObjective-Cのランタイムメカニズムを通じて解決され、文字列の解析、ランタイムルックアップ、型キャストが含まれます。KeyPathはネイティブのSwift構造に基づく静的なメカニズムです。

パフォーマンス比較

ベンチマークによると、KeyPathはプロパティアクセスにおいてvalue(forKey:)よりも平均で 10~20倍 高速です。この差は、ランタイムルックアップと動的ディスパッチがないことに起因します。KeyPathは、コンパイラの最適化が十分であれば、メモリ内のプロパティオフセットに直接アクセスします。

swift
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が唯一の正しい選択肢です。

よくある質問

簡単に言うと、SwiftのKeyPathとは何ですか?

KeyPath は、通常の値として渡すことができるオブジェクトのプロパティへのポインタです。person.nameと書く代わりに、\Person.nameというパスを作成し、任意のPersonインスタンスのプロパティを読み書きするために使用します。

KeyPath、WritableKeyPath、ReferenceWritableKeyPathの違いは何ですか?

KeyPath — 読み取り専用。WritableKeyPath — inoutコンテキストを通じて値型(struct)の読み取りと書き込み用。ReferenceWritableKeyPath — 追加条件なしで参照型(class)の読み取りと書き込み用。

SwiftでKeyPathはどこで使用されますか?

KeyPathは Combine(assign(to:on:))、SwiftUI(Binding、FocusState)、コレクションのソート(sorted(by:))、Core Data(NSSortDescriptor)、ネストされたプロパティへの安全なアクセスで広く使用されています。

KeyPathはvalue(forKey:)よりも安全ですか?

はい、KeyPathは完全に型安全です。パスのエラー(タイプミス、型の誤り)は実行時ではなくコンパイル時に検出されます。value(forKey:)は文字列を使用し、キーが存在しない場合にNSUndefinedKeyExceptionでクラッシュする可能性があります。

KeyPathを動的に作成できますか?

直接はできません — Swiftは文字列からの動的なKeyPath作成をサポートしていません。ただし、AnyKeyPath を使用して静的に取得したKeyPathを格納および渡すことができます。文字列による動的プロパティアクセスには、Objective-Cランタイムでvalue(forKey:)を使用してください。

まとめ

  • KeyPath — プロパティへの型付きパス、ファーストクラスの値型として表現
  • 階層 KeyPathには異なるアクセスレベルのためのWritableKeyPathとReferenceWritableKeyPathが含まれる
  • CombineとSwiftUI はリアクティブバインディングにKeyPathを積極的に使用
  • ソートとフィルタリング KeyPathを使用するとコードの可読性と型安全性が向上
  • AnyKeyPath — コレクションに異種KeyPathを格納するための型消去バリアント
  • パフォーマンス KeyPathはvalue(forKey:)よりも10~20倍高速

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください