00:06 Let's continue working on the running tracks app Laufpark. We've made a few cosmetic changes to the track info view since the last episode so that it shows more information, but everything still works the same way. Today we'll do an architectural experiment.
00:24 When the app launches, it shows a loading indicator until the GPX data is loaded. This is done in a standard way, but the code is spread out over four places: we create a UIActivityIndicatorView in the view controller, we do some configuration in viewDidLoad, we add the view to the layout a bit further down, and in the update method we start and stop the indicator:
01:35 It's easy to understand how the program works, but wouldn't it be nice if we could define the loading indicator in one place? Instead of explicitly telling the indicator to start and stop animating when the state changes, we want the indicator to react to a state change. We need a reactive approach for this.
02:15 We're going to use a small library called Incremental, which is similar to reactive programming but more experimental. In a later episode, we want to go into more detail about Incremental, but for now we can rely on what we know from reactive programming: working with mutable variables that can be observed.
02:48 We first refactor the view controller's state property. Eventually we want to get rid of it entirely, but we'll do this progressively. We rename the property to _state, so anytime we see code that uses this variable, we're reminded we should refactor that code. Until everything is updated, we'll have a hybrid architecture with two state properties.
03:24 We import Incremental and we need two new properties — an input and an observable. Every time we set our old version state, we also forward the value to the new input:
04:01 The Input property is similar to ReactiveSwift's mutable property or RxSwift's variable. The new state is an observable property (comparable to a signal) and its type is called I in incremental programming. We define it as a computed property that returns stateInput.i — the naming isn't perfect but it works for now.
04:54 So far, the code still builds and we haven't really changed anything. But now we can make use of our new observable state and move some view updating code from the update method to the place where the views are defined.
Gathering All Code
05:16 We put all loading indicator code together in one place, in viewDidLoad, and start observing the state. We get the loading boolean out of state and observe it with a closure. We have to pass unowned self to the closure; the closure shouldn't point to the view controller, because the view controller will eventually store a disposable that points back to the observer.
07:08 We add an array to the view controller to store the disposable in. This will be the place where we store anything we have to keep a reference to:
07:50 We remove the line where we start animating the indicator at setup, because the observer will be called immediately, thus starting the animation. When we run the app, we see the loading indicator appear and disappear, so everything works as expected.
08:18 There's just one piece of code that isn't local yet — the line where we instantiate the loading indicator. We convert the property into a local variable, which allows us to remove self from the observer closure again:
08:55 Everything concerning the loading indicator is now in one place: its declaration, its configuration, its constraints, and the observing. If we later want to remove the loading indicator for some reason, this single block of code can be removed and we'd be done.
09:21 Another improvement would be to write the code in a more declarative way. Currently we still call the methods to start and stop animating based on the state — a kind of glue code that we want to eliminate from viewDidLoad by binding the animating of the indicator to the loading boolean of the state. Almost every reactive library has a convenient way to bind a signal to a property. They usually do this at runtime in order to keep references to disposables. We'll take a different approach and create a box around the indicator view that also holds the disposables.
10:54 The box can hold anything, and we call its value unbox because that works nicely at the call site:
11:59 We can now create the box around the loading indicator and add the observer disposable to the box as well. At this point, the box still feels pointless, because we now have to add the box to the view controller's disposable. We clean that up later:
13:26 Let's clean the code up more. If we take out the isLoading observable, it becomes clear that we can move the whole boolean observer into the indicator view and provide an interface to bind the animation to any boolean:
16:32 The last ugly part we want to remove is where we add the boxed loading indicator to the view controller's disposables. While we continue to refactor the view controller, more and more views will be wrapped in a box, and with each box comes another disposable. By boxing up the root view, we could write our own "add boxed subview" method that will add the subview and store the box disposable for us.
17:26 We repurpose the disposables array property of the view controller to become a root view. Then in viewDidLoad, we can add the boxed loading indicator to this root view:
19:32 In the context of a Box, the term disposables doesn't make much sense, so we rename the property to references.
20:52 Looking at the loading indicator code in viewDidLoad, it's pretty clear code that also describes all the updates that will happen with the view. So it's declarative but still transparent enough to understand everything that's going on.
21:21 There are more ways to improve the code, like adding a constructor function that creates and configures a boxed activity indicator. Constructing the layout constraints makes for pretty verbose code that we need for every single view — we'll look at refactoring this next time.