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 onletproperties.WritableKeyPath<Root, Value>— read and write on avarof a value type; you need avarinstance to assign through it.ReferenceWritableKeyPath<Root, Value>— write through aletreference, 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.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…