Keypaths

Keypaths has been around for a while, and in Swift 5.2 got an upgrade which will help us to work together with SwiftUI and Combine, so let’s take a look and learn a little bit more.

Contents of this article:

  • What are Keypaths for?
  • Working with Keypaths
  • Conclusions

What are Keypaths

Keypaths are references to a property of a type, that allow to work with them afterwards. Keypaths are read only, if we would like to read and write form the resulting value, there are two other types:

WritableKeyPath: Specific for value types, in which case, the accessed property has to be specified as mutable.

ReferenceWritableKeyPath: Same as WritableKeyPath but specific for reference types.

Working with Keypaths:

Nothing better than examples to wrap one´s head around.

So we have a type Movie, and we would like to access the unique identifier of the movie in our catalog, and work with the uuid afterwards.

struct Movie {
    let uuid: String
    let isLive: Bool
    let needsSuscription: Bool
}

let keyPathToUUID = \Movie.uuid

// Work with keypath

let movie = Movie(uuid: "yoMovie", isLive: false, needsSuscription: true)

// Read value

print (movie[keyPath: keyPathToUUID]) // Will print "yoMovie"

keyPathToUUID it is actually of type AnyKeyPath, which means it is a key path rooted on Any type to Any value, so when we reference the type from where they Keypaths is rooted, and the property rooted, it would be written like this:

let keyPathToUUID: AnyKeyPath = \Movie.uuid

It is possible to access the key path to the property, by referencing the type, like this:

let keyPathToUUID : PartialKeyPath<Movie> = \.uuid

If we would like to change the value, by declaring the instance as var and the properties as var, the Keypath would be of type WritableKeyPath :

struct Movie {
    var uuid: String
    let isLive: Bool
    let needsSuscription: Bool
}

var keyPathToUUID = \Movie.uuid

// Work with keypath

var movie = Movie(uuid: "yoMovie", isLive: false, needsSuscription: true)

// Write value

movie[keyPath: keyPathToUUID] = "daMovie"

print (movie[keyPath: keyPathToUUID]) // Will print "daMovie"

What if movie is a reference type?

class Movie {
    var uuid: String = "theMovie"
}

let keyPathToUUID = \Movie.uuid

// Work with keypath

let movie = Movie()

// Write value

movie[keyPath: keyPathToUUID] = "daMovie"

print (movie[keyPath: keyPathToUUID]) // Will print "daMovie"

This time, although Movie has been declared as let (but the property is declared as var), as the Movie is a class, it is possible to write the value of the property through the keypath.

Alright then, but what are they useful for? Can we just assign value to properties as usual and move on? Yeah, we could do that, but as we said per definition, Keypaths are a reference to a property of a type, and we don’t need an instance of this type to work with, and that is very powerful!

Lets say we have a screen where we would like to show Movie details, but in our catalog, we have as well Series, that shares some properties with Movies, and we would like to show the shared details in one screen:

struct Movie {
    var uuid: String
    var title: String
    var synopsis: String
    var needsSuscription: Bool
}

struct Series {
    var uuid: String
    var title: String
    var synopsis: String
    var isLive: Bool
    var needsSuscription: Bool
}

// Quick uiview for helping porpuses

class ContentDetailsView: UIView {
    let titleLabel = UILabel()
    let synopsisLabel = UILabel()
}

struct ViewConfigurator<Content> {
    let titleKeyPath: WritableKeyPath<Content, String>
    let synopsisKeyPath
        : WritableKeyPath<Content, String>
    
    func configureView(view: ContentDetailsView, content: Content) {
        view.titleLabel.text = content[keyPath: titleKeyPath]
        view.synopsisLabel.text = content[keyPath: synopsisKeyPath]
    }
}

let movieDetailConfigurator = ViewConfigurator<Movie>(
    titleKeyPath: \.title,
    synopsisKeyPath: \.synopsis
)

let seriesDetailConfigurator = ViewConfigurator<Series>(
    titleKeyPath: \.title,
    synopsisKeyPath: \.synopsis
)

Pretty neat right? This piece of code is inspired in an example gotten from SwiftBySundell.com, I liked it so much, and showed so well the beauty of Keypaths, that decided to base the main example of this article on it.

And last, but not least, in Swift 5.2 Keypaths got and upgrade, that is that we can pass them around as functions, like this:

let movies = [theMovie, yoMovie, daMovie]

let moviesSynopsis = movies.map(\.synopsis)

And this is how we usually would do without using Keypaths:

let moviesSynopsisOldWay = movies.map { $0.synopsis }

The general rule to pass Keypaths as functions is the function has to be to type (Root) -> Value, which means that Keypath we pass has to be able to extract the keypaths value and the returned value is valid.

Conclusion

In the way to explore SwiftUI and Combine, we have given another step forward setting the foundational knowledge, this time working with Keypaths, which it used a lot in SwiftUI and Combine. Next stop Type Erasures!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: