Skip to content
Lesson 2 of 7

Step 1 of 4 · Reading · ~2 min

Learn

Property Wrappers and Key Paths

A key path is a typed reference to a property — like a pointer to a getter/setter, but checked at compile time.

struct Person {
    var name: String
    var age: Int
}

let keyPath = \Person.name           // KeyPath<Person, String>

let ada = Person(name: "Ada", age: 36)
print(ada[keyPath: keyPath])           // Ada

The \Type.property syntax creates a key path. Apply it to an instance with instance[keyPath: kp]. Nothing is looked up by name at runtime — the compiler resolved it, so a typo is a build error, not a crash.

Why bother, when .name already works?

Because a key path is a value. You can store it, pass it to a function, and let the caller decide which property a generic routine should look at. $0.name is code; `Person.name` is data that happens to describe code.

struct Score { let player: String; let points: Int }

let scores = [Score(player: "Alice", points: 80),
              Score(player: "Bob", points: 95)]

let kp: KeyPath<Score, Int> = \Score.points
let ranked = scores.sorted { $0[keyPath: kp] > $1[keyPath: kp] }
print(ranked.map(\.player))     // ["Bob", "Alice"]

map(\.player) is shorthand for map { $0.player }. Same compile-time checking, less noise. Both forms above compile on this course's grader.

The three flavours

  • KeyPath<Root, Value> — read only. Works on let properties.
  • WritableKeyPath<Root, Value> — read and write on a var of a value type; you need a var instance to assign through it.
  • ReferenceWritableKeyPath<Root, Value> — write through a let reference, because a class instance can mutate without the binding changing.
let writeKp: WritableKeyPath<Person, String> = \Person.name
var p = Person(name: "Ada", age: 36)
p[keyPath: writeKp] = "Linus"
print(p.name)                          // Linus

Ask for the narrowest one the call needs. A function taking KeyPath accepts every key path; a function taking WritableKeyPath refuses \Person.constantField at compile time, which is exactly the error you want.

A version trap worth knowing

Modern Apple-platform code sorts with people.sorted(using: KeyPathComparator(\.age)). That comparator arrived with Swift 5.5 / macOS 12. This course's grader is Swift 5.2, where KeyPathComparator is an unresolved identifier — the closure form with [keyPath:] shown above is the one that builds here, and it is what the exercise expects.

Where key paths show up

  • Generic functions that operate on "whichever property you name" — sorting, grouping, diffing.
  • Combine's assign(to:on:) and SwiftUI bindings wire a publisher to a property with a key path.
  • Dependency-injection containers and CoreData/SwiftData change tracking key their bookkeeping on key paths.

Your exercise

You will sort scores highest-first by routing the comparison through a KeyPath<Score, Int> rather than writing $0.points directly. The mistake the grader catches is sorting ascending: read the expected output carefully — the top scorer prints first.

Up nextResult BuildersBuilders and Opaque Types

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…