In Swift, a key-path refers to a property or subscript of a type. You use key-path expressions in dynamic programming tasks, such as key-value observing.
Key paths are type-safe, which means the compiler checks that the key path you are using actually refers to a property or value that exists in the object. This helps prevent runtime errors and makes your code more reliable.
A key-path string expression, #keyPath()
, lets you access the string used to refer to a property in Objective-C, for use in key-value coding and key-value observing APIs. It has the following form:
#keyPath(<#property name#>)
The property name must be a reference to a property that’s available in the Objective-C runtime. At compile time, the key-path string expression is replaced by a string literal. For example:
import Foundation
class MyClass: NSObject {
@objc var myProperty: Int
init(myProperty: Int) {
self.myProperty = myProperty
}
}
let myObject = MyClass(myProperty: 42)
let myKeyPath = #keyPath(MyClass.myProperty)
if let myValue = myObject.value(forKey: myKeyPath) {
print(myValue)
}
// Prints "42"
The #keyPath()
syntax provides a convenient way to safely refer to properties. Unfortunately, once validated, the expression becomes a String
which has a number of important limitations:
KeyPaths
s are a family of generic classes which encapsulate a property reference or chain of property references, including the type, mutability, property name(s), and ability to set/get values.
The performance of interacting with a property/subscript via KeyPaths
should be close to the cost of calling the property directly.
A key-path expression takes the general form \<Type>.<path>
, where <Type>
is a type name, and <path>
is a chain of one or more property, subscript, or optional chaining/forcing operators. If the type name can be inferred from context, then it can be elided, leaving \.<path>
.
There’re several variants of KeyPath family:
struct Point {
let x: Double
let y: Double
}
let origin = Point(x: 0, y: 0)
// Create a keypath for the x property
let xKeyPath = \Point.x
// Use the keypath to get the x value of origin
let originX = origin[keyPath: xKeyPath]
print(originX) // 0
// Try to use the keypath to set the x value of origin
origin[keyPath: xKeyPath] = 1 // Error: Cannot assign to property: 'origin' is a 'let' constant
struct Person {
var name: String
var age: Int
}
var alice = Person(name: "Alice", age: 25)
// Create a keypath for the name property
let nameKeyPath = \Person.name
// Use the keypath to get the name value of alice
let aliceName = alice[keyPath: nameKeyPath]
print(aliceName) // Alice
// Use the keypath to set the name value of alice
alice[keyPath: nameKeyPath] = "Bob"
print(alice.name) // Bob
// Try to use the keypath with a constant instance
let bob = Person(name: "Bob", age: 30)
bob[keyPath: nameKeyPath] = "Alice" // Error: Cannot assign to property: 'bob' is a 'let' constant
class Book {
var title: String
var author: String
init(title: String, author: String) {
self.title = title
self.author = author
}
}
let book = Book(title: "1984", author: "George Orwell")
// Create a keypath for the title property
let titleKeyPath = \Book.title
// Use the keypath to get the title value of book
let bookTitle = book[keyPath: titleKeyPath]
print(bookTitle) // 1984
// Use the keypath to set the title value of book
book[keyPath: titleKeyPath] = "Animal Farm"
print(book.title) // Animal Farm
// Try to use the keypath with a constant property
book.author = "Eric Blair" // Error: Cannot assign to property: 'author' is a 'let' constant