Friday Nov 17, 2023
To celebrate the release of Swift macros we released updates to 4 of our popular libraries to greatly simplify and enhance their abilities: CasePaths, ComposableArchitecture, SwiftUINavigation, and Dependencies. Each day this week we detailed how macros have allowed us to massively simplify one of these libraries, and increase their powers.
Join us now for a recap of all the releases we had over the past week, and see just how powerful Swift macros can be.
The first release of the week brought a massive update to our CasePaths library. This library aims to bring many of the affordances of key paths to the cases of enums, but sadly it never really live up to its potential. Until now, that is.
By using the new @CasePathable
macro on enums you can obtain a key path for each case of the enum
that abstractly represents the two fundamental things one can do with an enum value: try to extract
a case’s value out of the enum, or embed a case’s value into the enum:
@CasePathable
enum Destination {
case activity(ActivityModel)
case settings(SettingsModel)
}
let activityPath = \Destination.Cases.activity // CaseKeyPath<Destination, ActivityModel>
This unlocks a lot of interesting possibilities in API design, but the most immediate benefit to
you is that you immediately get access to an is
method for determining if an enum value matches
a particular case:
let destination = Destination.activity(…)
if destination.is(\.activity) {
…
}
Further, if you apply the @dynamicMemberLookup
attribute to your enum:
@CasePathable
@dynamicMemberLookup
enum Destination {
…
}
…then you get instant access to a computed property for each of your enum’s cases:
let destination = Destination.activity(…)
destination.activity // Optional(ActivityModel)
destination.settings // nil
This gives you very easy access to the data in your enums, and you can even use key path syntax
when using standard library APIs, such as compactMap
:
let destinations: [Destination] = […]
let activityModels = destinations.compactMap(\.activity)
The second release we had this week was version 1.4 of the
Composable Architecture. This release introduced the @Reducer
macro that automates a
few things for you:
@Reducer
struct Feature {
…
}
It automatically applies the @CasePathable
macro to your Action
enum inside (and even State
if it’s an enum), and it lints for some simple gotchas that we can detect.
But most importantly, by using @CasePathable
on the feature’s enums we unlock simpler versions of
the APIs offered by the library. The various compositional operators, such as Scope
, ifLet
,
forEach
, etc. can now be written with simple key path syntax:
Reduce { state, action in
…
}
-.ifLet(\.child, action: /Action.child) {
+.ifLet(\.child, action: \.child) {
ChildFeature()
}
The navigation view modifiers that the library provides can be massively simplified. You can now perform the state transformation using a simple, more familiar key path syntax:
.sheet(
- store: self.store.scope(
- state: \.$destination,
- action: { .destination($0) }
- ),
- state: /Feature.Destination.State.editForm,
- action: Feature.Destination.Action.editForm
+ store: self.store.scope(
+ state: \.$destination,
+ action: { .destination($0) }
+ ),
+ state: \.editForm,
+ action: { .editForm($0) }
) { store in
EditForm(store: store)
}
And key paths have also allowed us to simplify how one asserts against actions received by effects while testing. Currently you must specify the exact, concrete action that is received by the test store, but now that can be shorted to the key path describing the case of the action enum:
-store.receive(.response(.success("Hello"))) {
+store.receive(\.response.success) {
…
}
This starts to really pay off when testing deeply nested actions, as is often the case with testing the integration of many features together:
-store.receive(.destination(.presented(.child(.response.success("Hello"))))) {
+store.receive(\.destination.child.response.success) {
$0.message = "Hello"
}
👋 Hey there! If you got this far, then you must have enjoyed this post. You may want to also check out Point-Free, a video series covering advanced programming topics in Swift. Consider subscribing today!