A key in front of a path, found on Unsplash

Practical Key Paths in Swift

Moritz Lang
Slashkeys Engineering
3 min readMay 31, 2018

--

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 the Validations 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 ✌️

--

--

Moritz Lang
Slashkeys Engineering

Swift Developer / Software Engineering student @codeuniversity