Keypath


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/