🎓 A1Learner
🔍
0 of 27 lessons completed

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?