Difficulty: Beginner | Easy | Normal | Challenging
This article has been developed using Xcode 11.4.1, and Swift 5.2.2
- You will be expected to be aware how to make a Single View Application, or a Playground to run Swift code
keypath: Read-only access to a property writablekeypath: read-write access to a value-type property referencewritablekeypath: read-write access to a reference-type property
Combine
uses ReferenceWritableKeyPath<Root, Bool>
in the assign function
func assign<Root>(to keyPath: ReferenceWritableKeyPath<Root, Bool>, on object: Root) -> AnyCancellable
which is the one that allows us to connect to UIKit
elements, for example
validationSub = loginViewModel?.userValidation
.receive(on: RunLoop.main)
.assign(to: \.isEnabled, on: loginView.loginButton)
That seems fine, but what are keypaths? How are they used, and what do they mean?
Could you make an article explaining this for me?
I don't mind if I do!
A keypath provides read-only access to a property, whilst a writable keypath provides (well...) writable access to a property.
Perhaps the best way to describe this access is through an example, where we can set up a rather basic struct
object.
struct Person {
var firstname: String
var secondname: String
var age: Int
}
let dave = Person(firstname: "Dave", secondname: "Trencher" , age: 21)
we can then access the properties through WritableKeyPath<Person, String>
or WritableKeyPath<Person, Int>
(where firstname and secondname are represented by a String
and age is represeted by a Int
).
The following keypath therefore returns a String
, and that can be printed out
let firstname: String = dave[keyPath: \Person.firstname]
print (firstname) // Dave
Now even the property type can be stored
var writableKeyPathFirstName: WritableKeyPath<Person, String> = \Person.firstname
print (dave[keyPath: writableKeyPathFirstName]) // Dave
which means that you can potentially use the same property in multiple places, and storing it as a property itself means that it would only need to be stored in one place.
The following is an example of nested keypaths
struct Socks {
var sockname: String
}
struct DrawContents {
var name: String
var socks: Socks
}
let topdrawer = DrawContents(name: "top", socks: Socks(sockname: "Birthday Socks"))
print (topdrawer)
print (topdrawer[keyPath: \DrawContents.name]) // top
print (topdrawer[keyPath: \DrawContents.socks.sockname]) // Birthday Socks
Swift allows you to dynamically combine keypaths at runtime (of course the types need to be compatible).
let topdrawerkpath = \DrawContents.socks
let sockspath = \Socks.sockname
let composedPath: WritableKeyPath<DrawContents, String> = topdrawerkpath.appending(path: sockspath)
You may wish to have a keypath that does not require the Value parameter
let drawerName: PartialKeyPath<DrawContents> = \.name
let drawerSocks: PartialKeyPath<DrawContents> = \.socks
So both drawerName
and drawerSocks
can be stored with the same type: PartialKeyPath<DrawContents>
. The Value
parameter has been type-erased.
This shouldn't be a too big surprise, but if you use a keypath on a reference type (for example a class
)
let horse = Animal(name: "Keith")
var refKeyPath: ReferenceWritableKeyPath<Animal, String> = \Animal.name
let animalname: String = horse[keyPath: \Animal.name] // Keith
Keypaths have been around for some time, they're present in Objective-C
! However, they were not type safe (keyPath()
is actually a String
).
#keyPath(Person.firstname)
Key path cannot refer to static member 'lifeform'
If we change Person
to have a static var.
struct Person {
static var lifeform = "Carbon"
var firstname: String
var secondname: String
var age: Int
}
dave[keyPath: \Person.lifeform] // Key path cannot refer to static member 'lifeform'
As, well, keypaths cannot refer to static members! What a shame!
So, keypaths are actually useful in iOS development, being relevant for Combine
and SwiftUI. You want to buckle up and become familiar with this, or not? I'd say that the former is more important - and you can read up on this HERE.
I hope that this article has helped you out in become more familar in this relatively new feature of Swift.
If you've any questions, comments or suggestions please hit me up on Twitter