My notes on keypath
- KeyPath: Provides read-only access to a property.
- WritableKeyPath: Provides readwrite access to a mutable property with value semantics (so the instance in question also needs to be mutable for writes to be allowed).
- ReferenceWritableKeyPath: Can only be used with reference types (such as instances of a class), and provides readwrite access to any mutable property.
basics:
struct Starship {
var name: String
var maxWarp: Double
}
let voyager = Starship(name: “Voyager”, maxWarp: 9.975) Keypaths let us refer to the name or maxWarp properties without reading them directly, like this:
let nameKeyPath = \Starship.name
let warpKeyPath = \Starship.maxWarp
If you want to read those keypaths on a specific starship, Swift will return you the actual values attached to those properties:
let nameKeyPath = \Starship.name
let warpKeyPath = \Starship.maxWarp
print(voyager[keyPath: nameKeyPath])
print(voyager[keyPath: warpKeyPath])
Shorthand
When using KeyPaths within an object definition itself, you can omit the definition name, like this:
class Doll {
//...
func getMaker() -> String {
return self[keyPath: \.maker]
}
}
Accessor:
func getPropertyValue(in doll: Doll2, keyPath: KeyPath<Doll2, String>) -> String {
return doll[keyPath: keyPath]
}
Advance usage:
Let’s create a generic type called CellConfigurator, and since we want to render different data for different models, we’ll give it a set of key path-based properties — one for each piece of data that we’re looking to render:
struct CellConfigurator<Model> {
let titleKeyPath: KeyPath<Model, String>
let subtitleKeyPath: KeyPath<Model, String>
let imageKeyPath: KeyPath<Model, UIImage?>
func configure(_ cell: UITableViewCell, for model: Model) {
cell.textLabel?.text = model[keyPath: titleKeyPath]
cell.detailTextLabel?.text = model[keyPath: subtitleKeyPath]
cell.imageView?.image = model[keyPath: imageKeyPath]
}
}
The beauty of the above approach is that we can now easily specialize our generic CellConfigurator for each model using the same lightweight key path syntax from before — like this:
let songCellConfigurator = CellConfigurator<Song>(
titleKeyPath: \.name,
subtitleKeyPath: \.artistName,
imageKeyPath: \.albumArtwork
)
let playlistCellConfigurator = CellConfigurator<Playlist>(
titleKeyPath: \.title,
subtitleKeyPath: \.authorName,
imageKeyPath: \.artwork
)
Nuances:
We have three read-only key paths.
KeyPath ParialKeyPath AnyKeyPath And two writable key paths.
WritableKeyPath ReferenceWritableKeyPath I will only focus on three basic types of key paths in this article.
KeyPath: A read-only access to a property. Root type can be both value/reference semantics. WritableKeyPath: Provides read-write access to a mutable property with value semantics (such as struct and enum). ReferenceWritableKeyPath: Provides reading and writing to a mutable property with reference semantics (such as class). We won’t talk about ParialKeyPath and AnyKeyPath here since it is another type-erased variation of KeyPath.
KeyPath resources
https://www.andyibanez.com/posts/understanding-keypaths-swift/
https://www.hackingwithswift.com/example-code/language/what-are-keypaths
https://www.swiftbysundell.com/articles/the-power-of-key-paths-in-swift/
https://sarunw.com/posts/what-is-keypath-in-swift/