KeyPaths and Pattern Matching (with version cues)#
How to leverage key paths and pattern matching to write concise, readable Swift.
Version Quick Reference#
- Swift 4+: writable key paths,
\.property syntax. - Swift 5.x+:
KeyPath improvements and pattern matching ergonomics. - Swift 5.7+:
any spelling for existentials can appear with KeyPath constraints.
KeyPath Basics#
- Use
\Type.property to reference properties without calling them. Great for mapping, sorting, and dependency injection.
1
2
3
4
| struct User { let name: String; let age: Int }
let names = users.map(\.name)
let sorted = users.sorted(by: \.age) // via overload taking a key path
|
- For mutation, use
WritableKeyPath and ReferenceWritableKeyPath (classes).
1
2
3
| func set<Value>(_ keyPath: WritableKeyPath<User, Value>, to value: Value, on user: inout User) {
user[keyPath: keyPath] = value
}
|
Dynamic Member Lookup#
- Some types expose dynamic members that forward through key paths (e.g.,
SwiftUI.Binding). You can build your own with @dynamicMemberLookup and subscript overloads taking key paths.
Pattern Matching Essentials#
switch supports case, where clauses, and pattern bindings for enums, optionals, tuples, ranges, and custom patterns.
1
2
3
4
5
6
7
8
| switch value {
case .success(let data):
handle(data)
case .failure(let error) where error.isRetriable:
retry()
case .failure:
break
}
|
if case / guard case#
- Use to destructure single values without a full
switch.
1
2
3
4
| if case .failure(let error) = result { print(error) }
guard case .success(let payload) = result else { return }
process(payload)
|
Optional Patterns#
- Match
.some/.none or use if case let x?.
1
| if case let value? = optionalValue { print(value) }
|
Tuple and Range Patterns#
- Destructure tuples in switches; match numeric ranges with
... or ..<.
1
2
3
4
5
6
| let point = (x: 3, y: 7)
switch point {
case (0, 0): print("origin")
case (1..., 1...): print("quadrant")
default: break
}
|
Custom Pattern Matching#
- Implement
~= (pattern:value:) to enable custom matching. Keep it lightweight and predictable.
1
2
3
4
5
6
7
| struct Prefix { let value: String }
func ~=(pattern: Prefix, value: String) -> Bool { value.hasPrefix(pattern.value) }
switch "swift-notes" {
case Prefix("swift"): print("matches")
default: break
}
|
Tips#
- Prefer exhaustive
switch on enums to catch new cases at compile time. - Combine
where clauses for readability instead of nesting conditionals. - Keep custom patterns simple to avoid surprising control flow.