KeyPath is a typed path to an object’s property in Swift, represented as a first-class value. Unlike string keys in KVC (Key-Value Coding), KeyPath is checked by the compiler at build time: the compiler knows the root object type, the property type, and can guarantee that the path exists. KeyPath is used in Combine for reactive binding, in SwiftUI for two-way binding, and in the standard library for sorting and filtering collections. According to Apple Developer, 2025, KeyPath is a fundamental building block for functional programming in Swift.
Key Takeaways
KeyPath is a type from the Swift standard library that represents a path to an object’s property. Unlike manually accessing a property with dot notation (object.property) or a string key (value(forKey:)), KeyPath is a first-class object: it can be passed as an argument, stored in a variable, and composed.
A KeyPath is written with a backslash followed by the path to the property: \Person.name. The path can be nested: \Person.address.city. The compiler verifies that Person has a name property and that name is indeed a String. If the property does not exist or the type is wrong, the code does not compile.
struct Person: Codable {
let name: String
let age: Int
var isActive: Bool
}
// KeyPath (read-only)
let namePath: KeyPath<Person, String> = \Person.name
let person = Person(name: "Alice", age: 30, isActive: true)
let name = person[keyPath: namePath]
Accessing a property via KeyPath is done through the subscript object[keyPath: path]. For read-only properties, KeyPath is used. If the property is mutable (var), WritableKeyPath is available. For reference types, ReferenceWritableKeyPath allows modifying the property directly through the KeyPath.
KeyPath has a subtype hierarchy reflecting the access level to the property. The base hierarchy is: AnyKeyPath → PartialKeyPath → KeyPath → WritableKeyPath → ReferenceWritableKeyPath. Each subtype adds capabilities.
KeyPath — read-only. WritableKeyPath — read and write for value types (struct). ReferenceWritableKeyPath — read and write for reference types (class). Swift automatically selects the correct subtype based on context.
| Type | Access | Object Type | Mutation |
|---|---|---|---|
| KeyPath | ReadOnly | Any | No |
| WritableKeyPath | ReadWrite | Value type (inout) | Through mutating context |
| ReferenceWritableKeyPath | ReadWrite | Reference type | Direct assignment |
// WritableKeyPath for value type
var mutablePerson = Person(name: "Bob", age: 25, isActive: false)
let writablePath: WritableKeyPath<Person, Bool> = \Person.isActive
mutablePerson[keyPath: writablePath] = true
// ReferenceWritableKeyPath for class
class User: NSObject {
@objc dynamic var name: String = ""
}
let user = User()
let refPath: ReferenceWritableKeyPath<User, String> = \User.name
user[keyPath: refPath] = "Charlie"
An important distinction: for value types, setting via KeyPath requires var (inout context), while for reference types, only a mutable property is needed. This aligns with Swift’s general semantics: value types are passed by value, so mutation requires container mutability.
KeyPath plays a central role in Combine and SwiftUI. In Combine, the assign operator uses KeyPath to bind a publisher’s value to an object’s property. In SwiftUI, Binding uses KeyPath for two-way linking between model and view.
The assign(to:on:) operator accepts a ReferenceWritableKeyPath and an object. When the publisher emits a value, it is automatically written to the specified property. This is a declarative way to manage state without manual assignments.
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)
}
}
In SwiftUI, @Binding uses KeyPath to connect a parent and child View. The parent passes a Binding
KeyPath enables writing flexible and type-safe sorting and filtering functions. Instead of passing a closure each time, you can pass a KeyPath to the property by which you want to sort the collection. This makes the code cleaner and reduces duplication.
The function 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)
This implementation sorts by any Comparable property without writing closures. To sort by last name, just change the KeyPath: \Person.lastName. If sorting by a custom key (e.g., name length), a map is used inside — the developer can still write their own closure for non-standard cases.
AnyKeyPath is the base type in the KeyPath hierarchy that erases information about the specific root and value types. AnyKeyPath does not know which object type it is called on or what value type it returns. This is useful for storing heterogeneous KeyPaths in collections and for reflection.
AnyKeyPath is used when you need to store a list of paths to different properties of different types. For example, in a UI configurator, where each path configures a specific property, and the property types may differ. AnyKeyPath allows working uniformly with all paths.
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 { }
// Allows configuring properties via AnyKeyPath
AnyKeyPath requires type casting when retrieving the value, since the specific type is erased. For full type safety, a typed KeyPath
KeyPath is significantly more performant than KVC string keys (value(forKey:)). String keys are resolved through the Objective-C runtime mechanism, including string parsing, runtime lookup, and type casting. KeyPath is a static mechanism based on native Swift structures.
According to benchmarks, KeyPath is on average 10-20 times faster than value(forKey:) for property access. The difference is due to the absence of runtime lookup and dynamic dispatch. KeyPath directly accesses the property offset in memory with sufficient compiler optimization.
func readWithKeyPath(person: Person) -> String {
person[keyPath: \Person.name]
}
func readWithKVC(person: Person) -> String {
person.value(forKey: "name") as! String
}
KVC is not only slower but also type-unsafe: the string “name” may not exist, and the as! String cast may crash. KeyPath guarantees the existence of the property and type correctness at compile time. For performance-critical sections (animations, lists with thousands of items), KeyPath is the only correct choice.
Frequently Asked Questions
KeyPath is a pointer to an object’s property that can be passed as a regular value. Instead of writing person.name, you create a path \Person.name and use it to read or write the property on any Person instance.
KeyPath is read-only. WritableKeyPath is for reading and writing value types (struct) through inout context. ReferenceWritableKeyPath is for reading and writing reference types (class) without additional conditions.
KeyPath is widely used in Combine (assign(to:on:)), SwiftUI (Binding, FocusState), collection sorting (sorted(by:)), Core Data (NSSortDescriptor), and for safe access to nested properties.
Yes, KeyPath is fully type-safe. An error in the path (typo, wrong type) is caught at compile time, not at runtime. value(forKey:) uses strings and can crash with NSUndefinedKeyException if the key does not exist.
Not directly — Swift does not support dynamic KeyPath creation from a string. However, you can use AnyKeyPath to store and pass statically obtained KeyPaths. For dynamic property access by string, use value(forKey:) with the Objective-C runtime.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also