Swift Key Paths
A key path is a value that points at a property of a type without naming an instance. One key path can read that property from many instances, and a writable key path can change it.
Reading through key path literals
Two key path literals point at the name and the age of the Person type. Reading the instance through each path prints the matching stored value.
struct Person {
var name: String
var age: Int
}
let person = Person(name: "Ana", age: 31)
let namePath = \Person.name
let agePath = \Person.age
print(person[keyPath: namePath])
print(person[keyPath: agePath])A stored KeyPath passed to a function
The age path is stored with its full KeyPath type. The describe function takes that path as a parameter and reads the age of whichever person it is given.
struct Person {
var name: String
var age: Int
}
let agePath: KeyPath<Person, Int> = \Person.age
func describe(person: Person, path: KeyPath<Person, Int>) -> String {
return "Age \(person[keyPath: path])"
}
let people = [Person(name: "Ana", age: 31), Person(name: "Ben", age: 44)]
for person in people {
print(describe(person: person, path: agePath))
}Writing through a writable key path
Both paths here are stored as WritableKeyPath values. Assigning and adding through them changes the stored properties of the variable instance, and the direct reads show the new values.
struct Person {
var name: String
var age: Int
}
var person = Person(name: "Ana", age: 31)
let namePath: WritableKeyPath<Person, String> = \Person.name
let agePath: WritableKeyPath<Person, Int> = \Person.age
person[keyPath: namePath] = "Maya"
person[keyPath: agePath] += 1
print(person.name)
print(person.age)Appending key paths to reach a nested value
The User type holds an Address, so a single short path stops at the address itself. Appending an Address path to the User address path builds one path that reaches the city and the country.
struct Address {
var city: String
var country: String
}
struct User {
var name: String
var address: Address
}
let user = User(name: "Ana", address: Address(city: "Denver", country: "USA"))
let cityPath = (\User.address).appending(path: \Address.city)
let countryPath: KeyPath<User, String> = (\User.address).appending(path: \Address.country)
print(user[keyPath: cityPath])
print(user[keyPath: countryPath])Example
struct Address {
var city: String
var zip: String
}
struct Employee {
var name: String
var role: String
var address: Address
}
var employee = Employee(name: "Ben", role: "Clerk", address: Address(city: "Miami", zip: "33101"))
let rolePath: WritableKeyPath<Employee, String> = \Employee.role
employee[keyPath: rolePath] = "Manager"
let cityPath = (\Employee.address).appending(path: \Address.city)
print(employee[keyPath: \Employee.name])
print(employee.role)
print(employee[keyPath: cityPath])Key points
- A key path literal starts with a backslash, then the type name, then the member name.
- Reading through a key path uses the keyPath label inside the brackets.
- A stored key path has the KeyPath type, which names the root type and the value type.
- A WritableKeyPath can change the property it points at on a variable instance.
- Appending one key path to another builds a longer path that reaches through a nested value.
- The same key path value can be passed to a function and reused for every instance.
In this example, three key paths do three different jobs on one employee value. A literal path reads the name, a stored writable path changes the role to Manager, and an appended path reaches through the address to read the city. Each printed line comes from a different kind of path working on the same instance.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
A program needs to change a property through a stored key path. Which type must the path have?