Practical Key Paths in Swift
Although Key Paths are one of the most popular features introduced by Swift 4 I always struggled to integrate them in my day-to-day code. That’s why, in this post, I challenged myself to write about 3 practical use-cases of Key Paths. Before reading on I recommend to first learn about Generics & Generic types.
1. Vapor Validation
I discovered this first example while skimming through Vapor 3 on GitHub. They’ve written a very elegant validation API which uses Key Paths to add specific fields with their respective rules to the validation process of a specific type. Complicated enough? 😊 Let’s take a look!
class User: Validatable, Reflectable, Codable {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
static func validations() throws -> Validations<User> {
var validations = Validations(User.self)
// validate name is at least 5 characters and alphanumeric
try validations.add(\.name, .count(5...) && .alphanumeric)
// validate age is 18 or older
try validations.add(\.age, .range(18...))
return validations
}
}
In this small excerpt of a unit test inside Vapor’s Validation library, they used a combination of Key Paths and static, generic constrained, extensions on the Validator
type to add validations to the User
class. The add
method is declared like this:
public mutating func add<T>(_ keyPath: KeyPath<M, T>, _ validator: Validator<T>) throws
As you can see the associated type of Validator
is inferred by the Key Path to provide you with different Validator
extensions based on the Key Paths value type. 🧙♀️
In case you’re wondering about the mysterious
M
, it’s an associated type of theValidations
type.
2. AutoLayout wrapper
I stumbled across this gem while watching another awesome episode of Objc.io ’s Swift Talk. As part of the episode, they wrote convenience methods to work with AutoLayout in iOS. At the end they created a tiny wrapper for NSLayoutConstraint
which can be used like this:
superView.addSubview(subview, constraints: [
equal(\.centerXAnchor),
equal(\.centerYAnchor)
])
The implementation of equal(_:)
looks like the following:
typealias Constraint = (_ child: UIView, _ parent: UIView) -> NSLayoutConstraint
func equal<L, Axis>(_ to: KeyPath<UIView, L>) -> Constraint where L: NSLayoutAnchor<Axis> {
return { view, parent in
view[keyPath: to].constraint(equalTo: parent[keyPath: to])
}
}
As already seen in the first example the generic Axis
is inferred by the KeyPath
and then passed along as the associated type of NSLayoutAnchor
.
3. Identifiable with custom property
protocol Identifiable {
associatedtype ID
static var idKey: KeyPath<Self, ID> { get }
}
extension Identifiable where ID: Equatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs[keyPath: Self.idKey] == rhs[keyPath: Self.idKey]
}
}
struct User: Identifiable {
static let idKey = \User.id
let id: String
}
struct Car: Identifiable {
static let idKey = \Car.licensePlate
let licensePlate: String
}
let user = User(id: "abc")
let otherUser = User(id: "def")
user == otherUser
let car = Car(licensePlate: "AB-CD-1234")
let otherCar = Car(licensePlate: "EF-GH-5678")
car == otherCar
In this last example, I used a KeyPath
to set the ID
type on conforming types of Identifiable
based on the idKey
. Now, instead of having to use an id
key on every object that should be identifiable I can choose whatever fits best into the context. As an added bonus I added conformation to Equatable
whenever the ID
conforms to it. Inside the function, I then used the idKey
to retrieve the underlying value.
4. Your turn
What about you? What’re your favorite use-cases of Key Paths in Swift? I’d love to extend this list 😊
Conclusion
Hopefully, this post gave you a brief overview of some more real-life examples of Key Paths. It sure helped me to understand them better.
Thanks for reading and happy coding ✌️