Next, let's talk about Composable Architecture.
Since I only have a limited time in this talk and there is also another session for this specific topic, I will only cover the very key points of Composable Architecture that are interesting and unique from other architectures.
The overview of Composable Architecture will look like this slide.
As most of you probably know, this framework is developed by Point-Free team: Brandon Williams and Stephen Celis, which you can find their online video tutorials and documentation in the official homepage.
It is also Elm-like Architecture and built on top of SwiftUI and Combine,
so the basic concept is very similar to Harvest.
The main differences are as follows:
- Composable Architecture adopts Multi-Store architecture
- Swift Case Paths (Smart Prism)
Let's take a look at one by one.
This is the diagram of the SwiftUI view tree with Composable Architecture's
Store attached to each view.
We have already seen the similar diagram in Harvest, but the difference here is that, in Composable Architecture, child view will also own its
Store rather than borrowing the partial reference from the root store.
Store in a child view owns a partial copy of the parent's state, and synchronizes the data reactively using Combine.
By the way, each
Store is actually a wrapper called
ViewStore which I will mention in a minute.
Now, let's take a look at what happens when child view receives events (actions) from a user.
Unlike Harvest where actions will be sent directly up to the root node, Composable Architecture will send them 1-level up to the parent node instead, and then that parent will also send the actions up to the grandparent, and so on.
After actions reach the toplevel root store, its reducer will be invoked and generates a new state.
It will be set in root store, and then FRP takes care of sending a new partial state down to the child stores.
This is what I meant by reactive synchronization.
So, the dataflow in Composable Architecture is actually a parent-child communication (not a simple unidirectional dataflow), and this messaging scheme is more like React than Elm.
By the way, there is a caution that when child store sends action to the parent, that parent store will also invoke its own reducer as well, updating its internal state.
Eventually, there will be a possibility of the intermediate view getting re-rendered multiple times by 1.
AppView's update and re-rendering children, and 2. intermediate store updates its state and re-renders itself.
So how Composable Architecture solves this problem is by adding
ViewStore as a store wrapper to ignore the duplication by calling Combine's
In my opinion, using
removeDuplicates is a bit costly operation (both in memory cache cost and the requirement of state's
Equatable check), but there might be some hidden advantages that I don't know yet, so for now, I won't go too detail here.
Next, let's talk about Case Paths.
In short, if we have
struct's data accessors (getter and setter),
CasePath is like data accessors for
And actually, we have already seen this in Optics!
WritableKeyPath (getter and setter) is really just a
CasePath can be thought as
Normally, when we use
WritableKeyPath, we use backslash character e.g.
\.someMember as a syntax sugar.
CasePath, on the other hand, it has a custom
prefix func / so that we can call e.g.
WritableKeyPath are dual to each other, using a forward-slash instead of backslash looks like a dual syntax which I like about this syntax.
Now, let's take a look at the structure of
Then we will see 2 functions:
extract, and this is exactly the same shape as
That means, to understand more about
CasePath, it's good to know about Optics too.
What's even more nicer about
CasePath is that it's not just a simple
Prism replacement: We can call it as Smart Prism.
What do I mean by that?
Prism normally requires 2 functions as I mentioned above, but for
CasePath, we only require
embed function so that
extract can be automagically derived from it.
How is that possible?
The secret is in the code below, which uses Swift
Mirror (reflection) and C memory access operations.
It's magic inside 🎩
By using this technique, we can remove a lot of boilerplate code to setup all possible
Prism instances without using a code-generation e.g. swift-enum-properties.
So, to summarize, "Multi-Store" and "Case Paths" are the 2 points that I found interesting in Composable Architecture.
To know more about it, please also check out another Composable Architecture session by @yimajo for more details (NOTE: It's in Japanese)