Unlock This Episode
Our Free plan includes 1 subscriber-only episode of your choice, plus weekly updates from our newsletter.
There’s a large complex problem that we all grapple with when making applications, but it’s not often addressed head on and given a systematic study. Our applications are built as large blobs of state, which represents the actual data of the application, and behavior to evolve that state over time. Now this big blob of data and behavior is extremely difficult to understand all at once. It’s of course all there, even if some parts are hidden away in little implicit chunks of state and behavior, but ideally we could cook up tools and techniques that allow us to understand only a small part of the application at once.
Doing this comes with huge benefits. Things like improved compile times, allowing yourself to build and run subsets of your application in isolation, strengthening the boundaries between components to make them more versatile, and more. We’ve definitely harped on these concepts over and over on Point-Free, but there’s still more to say.
We want to explore the heart of this problem from first principles. We’ll start with a vanilla SwiftUI application that has two separate screens, each with their own behavior and functionality, but they also need to share a little bit of functionality. Further, the parent screen that encapsulates the two children also wants to be able to observe the changes in each of the children.
This is a surprisingly subtle interaction to get right, especially in vanilla SwiftUI. The crux of the problem is that we want to be able to bundle up most or all of our application’s behavior into a single object, and then derive new objects from it that contain only a subset of the behavior. So, we could take the root application domain and derive smaller and smaller domains. For example, we take the app-level object and derive an object for just the home screen, and then further derive from that an object for the profile screen, and then further derive from that an object for the settings screen, all the while these derived objects will stay in sync with each other so that the changes in one are instantly reflected in the others.
Now, if you are a user of our Composable Architecture library this all probably sounds very similar to the concept of
.scopes, and you’re right, but we want to use vanilla SwiftUI as a jumping off point to dig even deeper into those concepts.
In this episode we only considered
@ObservedObject, but there’s also something called
@StateObject that can be handy for giving some behavior responsibilities to a child domain. This article describes in detail how each of the objects work under the hood and when it’s appropriate to use each one.
In this WWDC session from 2020 Apple engineers describe how to best wield
@StateObjects. Starting at around 12:30 in the video they hint at the possibility of breaking up large observable objects into smaller “projections”, but stop short of showing code on how to accomplish this and never released the source code of the demo project unfortunately. Hopefully WWDC 2021 will bring some solutions 🤞.
This is one of the few articles in the community that addresses how to derive child behavior from a parent. This article focuses on how to notify the parent when child state changes, but extra work must be done if one wants to share state between child and parent.
In the absence of Apple providing source code for the demo application used in Data Essentials many have wondered what Apple had in mind for the solution. This Twitter thread details some people’s conjectures, including Daniel Peter’s conjecture which is almost exactly the solution we came up with in this episode.