Integrating RxSwift Into Your Brain and Code Base: Part 2

Daniel Tartaglia
10 min readApr 13, 2019

In part 1 of this series we took one of Apple’s sample apps and added RxSwift to it. In doing so, we replaced all the IBActions, Delegates, Notifications, and KVO calls with their RxSwift counterparts. However, we didn’t really change the semantics of the code, it’s still just as imperative and one could even argue that it’s more confusing than the original. You might recall that in part 1 I commented that a lot of the complexity of an iOS app comes from having to integrate all the different observation systems into a cohesive whole. Now that we are only using one observation system, let’s see what we can do to get rid of that extra complexity and along the way, make our code more declarative. We will even write tests for the logic as we break it away from the effects the code performs.

As a reminder, you need to follow along in the GitHub repo in order to get the best out of this article. The first eight commits in the repo are covered in part 1 so let’s start there. Enough preamble, we’re off…

Consolidating the ProgressObserver

Since this is more about changing the semantics of the code, the process is going to be less formulaic. Basically we are looking for places where we are binding/subscribing inside another bind/subscribe, or where we are manipulating a Subject inside a bind/subscribe. For this first step I will focus on the ProgressObserver class since it only has two binds in it. We’ll start small.

Here, the only function is the init and the exposed properties are all lets. This immediately tells us that this entire class could likely be replaced by a single function that takes in the same parameters as the init method and returns the properties as a tuple. Further investigation reveals that name and progress properties aren’t used outside the class and could be made private so the only property we actually need to worry about is the changed computed property. Basically, the only reason this class existed in the original code was to convert from using KVO to a delegate. Now that such conversion is unnecessary, the entire class has become superflous.

As a result, the class functionality has been moved into a Progress extension function. Tests have also been written to ensure correct logic.

Simplifying the ProgressView

Let’s continue to follow the chain from the Progress object into the ProgressView.

First we inline the three functions that used to be a part of the delegate by moving their contents into the subscribe method. We see that there are no Subjects in them, but the terminus in the subscribe’s onNext closure is already a bindable property provided by RxCocoa, so we can make some simplifications there. We also find that the onError and onCompleted closures are merely debug messages. In the interest of code parity I will keep the print messages, the do() operator is great for such things.

Simplifying the SettingsViewController

Boy there’s a lot of logic in this class. The astute among you will notice that the displayName and serviceType validation doesn’t seem to exist in the Objective-C sample code from Apple. They checked validation by using that language’s try/catch system which isn’t available in Swift so I resorted to manually writing the validation logic. It would be nice though if it were more testable. So we will now move it to its own function. There are a lot of conditionals in this function and to achieve basis path coverage would require 13 tests. I’ll leave that as an exercise for the reader. At least now the logic is out where it can be tested without creating a SettingsViewController.

Further inspection shows that, though the done button’s bind function uses self, it does so in order to access the displayName and serviceType strings. So let’s expand our chain to push just those values in. Once we do this and then move the doneTapped code into the bind's closure we end up with:

let inputValues = Observable.combineLatest(displayNameTextField.rx.text.orEmpty, serviceTypeTextField.rx.text.orEmpty) { (displayName: $0, serviceType: $1) }doneButton.rx.tap
.withLatestFrom(inputValues)
.bind(onNext: { [weak self] displayName, serviceType in
if
isValid(displayName: displayName, serviceType: serviceType) {
self?._didCreateChatRoom.onNext((displayName, serviceType: serviceType))
}
else {
let alert = UIAlertController(title: "Error", message: "You must set a valid room name and your display name", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
self?.present(alert, animated: true, completion: nil)
}
})
.disposed(by: disposeBag)

Whenever I see an if...else block in a bind/subscribe function I immediately look to using Observable.filter instead. This will mean we need two observables, one for the true branch and one for the false branch. In the true branch we are just connecting to the subject so we can bind to it directly. In this instance we can’t get rid of the subject because the chain that feeds it doesn’t come into existence until after viewDidLoad is called.

Then there’s the matter of moving our logic out of the view controller so it can be tested independently. The inputs to the logic are the text of the two fields and the done button’s tap. The outputs are to inform the subject when a chat room can be created and to inform the view controller when an error alert needs to be displayed. Note, I didn’t say “either/or” here. The done button can be tapped multiple times and the function must be able to respond with both depending on the logic.

Congratulations are in order here. We just created our first view model.

Simplifying the SessionContainer `received` Observable

The first thing to do here is to inline the delegate functions into their function blocks. Once that is done, we can see that there are three different binds that all affect the _received subject. You want to avoid that as much as possible; it’s a very imperative thing to do. Better would be to have a single chain that expresses the invariant of when/why the subject should emit.

In all three cases, we are taking different sorts of data from the session object and converting them into Transcripts, and once we inline the functions we see that all the real work is being done inside the bind methods. Let’s extract that work into maps and then extract the logic into single, testable, operators. So that the only things left in the bind functions are the subject.

To complete this simplification, we only need to remove the binds, merge the three observables, and bind the merged observable once. Wait, we are in the init function, so we can get rid of the subject completly and directly assign to the received observable.

Simplifying the SessionContainer `didFinishReceivingResource` Closure

Here we see error objects, but we have to be careful because Observable errors will shutdown a stream and we don’t want that happening. Also, like earlier, we see a condition inside a bind. Actually, there are two ways the bind can error for a total of three different possible outputs.

Three different outputs calls for three different observable chains. Also, we see that a side effect (copying the image) happens where the results are required for later. Side effects are for beginnings and ends of observable chains, so we need to create an observable in which this side effect is performed. This became the copyItem(resourceName:localURL:) function. Since this effect can error, and we don’t want the error to break the chain, we use materialize() here.

We can’t get rid of the _update subject just yet because it is also used in the send(imageURL:) function.

Consolidating the MainViewController’s `sendMessageButton` Output

I see that this program suffers from “Massive View Controller” syndrome. Despite it’s small size, the main view controller is playing god. RxSwift isn’t an architecture and doesn’t avoid this anti-pattern on its own, but it does provide opportunities to use architectures that will help avoid the problem. That said, it isn’t the goal of this article to discuss architecture, we are only looking to simplify the code we have.

For a first step in simplifying the MainViewController, I see that there are two different observable chains that affect the sendMessageButton's isEnabled property. Let’s move those together. This is something I call Output Centric programming. The idea is that there is a single place in the code to see all the inputs that can affect a single piece of output. By doing this, the chain for that output becomes its invariant. It makes explicit exactly what behaviors affect this particular output and how they do so.

At this point, you might wonder about the text in messageComposeTextField. In one chain we listen to editingDidEnd and set the text to an empty string, while in the other chain we listen to the same event and read the text to use it. This is safe because programmatically changing the text does not cause an event to emit. That said, there is some odd behavior for the RxCocoa text attribute because it emits on editingDidEnd events. Since we want to emit false on editingDidEnd regardless of the contents of the field, we need to guard against that extra emission. Therefore we have to add the distinctUntilChanged() function.

Separating the text clearing behavior

In the spirit of the output centric approach mentioned above, we should limit the behavior in a subscribe/bind block as much as possible. To that end, let’s create a new observable chain just for clearing the text field.

Simplifying and Correcting the Send Text Feature

While writing the above and making the refactoring, I realized that the sample source has a rather subtle bug in it. The text is sent when the textField’s editingDidEnd event emits which happens when the field resigns first responder status. Tapping the send button or return on the keyboard causes the field to resign, but the field will also resign when some other view controller is presented (which would happen when the user taps one of the other buttons on the screen.) This surprising behavior slipped through the original sample because of the indirectness of the code. Do we really want to send whatever text is in the field whenever the keyboard drops? Not likely. Do we really want the keyboard to drop every time the user sends text? Also not likely.

Correct behavior is to send the text when the send button or return key is tapped and keep the keyboard up so the user is ready to send more text. We can fix that simply by replacing messageComposeTextField.rx.controlEvent(.editingDidEnd) with sendMessageButton.rx.tap and removing the code that causes the keyboard to dismiss in those situations. So let’s do that now.

Inlining All the Things!

You might have noticed that I’m doing a lot of inlining of functions in the main view controller. Let’s take a break from the hard stuff and inline all the functions that are only called once and only in an already existing closure. We will keep createSession() and insert(transcript:) for now since they are referenced from multiple places, but the rest of the private functions are going away.

Removing Nested Subscribes: `browseForPeers`

Seeing a disposeBag referenced inside a subscribe closure is a sure sign that we have some inapproprate nesting. I’ll start at the top and deal with the chain that presents the MCBrowserViewController.

The createWithParent function is cribbed from sample code that is distributed with the RxSwift package. There’s a lot of good stuff in there.

Extracting the UIAlertController Presentation

What happens when the sendPhotoButton is tapped? The first bit is easy, request authorization, but then we get into this massive bind closure. We know what happens inside that closure because we have run the program an know what needs to happen, but reading it is not very obvious. It creates and presents an action sheet to get configuration parameters for the image picker, then creates and presents the image picker to get the user’s image, then switches to a background thread to save the image and send it to the session container, then switches back to the main thread to insert the generated transcript into the table view. That’s entirely too much work for one closure.

Also, notice how I presented the behavior as a list of steps, but when you look at the code you see that these steps are deeply intwined. I have already written a blog post about this sort of code. That post was in the context of Promises, but the same ideas apply to Observables.

Fixing this closure is a big job so we will do it in stages starting at the top: presenting the UIAlertController to figure out how to configure the image picker.

Extracting the UIImagePickerController Presentation

This will be the third view controller that we have presented so it should be pretty obvious what’s going on here. I also cleaned up the UIAlertController presentation closure a bit.

Un-nesting `didFinishPickingMediaWithInfo`

The next step is small but highly instructive so I am giving it its own commit. Whenever you see a bind/subscribe where the first thing that happens within it is another bind/subscribe, you know a simple flatMap wrap like what I do in this commit is the right choice. Always keep an eye out for this sort of code.

Taking Advantage of RxSwift Async Abilities

With the above, we have gotten rid of all the nested binds but there’s still a lot of code in that bind closure. It dismisses the image picker, switches to a background thread to save and send the image then switches back to the foreground thread to insert the image into the tableView. Surely we can do more here…

In this commit we manage to tease out what little logic there is in this chain by creating the imageData(from:) and imageUrl(with:) functions, but this observable chain is almost exclusively side effects hence all the flatMaps.

We also found a couple of other places that were using DispatchQueues and changed them to use Schedulers.

Epilogue: What’s Left?

There’s certainly more that can be done! I haven’t wrapped the table view’s data for example. Also, I could re-architect the code to use a MVVM approach rather than its current ad-hoc setup. Lastly, I have separated some logic out of the main view controller but I didn’t add the tests yet.

I will likely refine the code when time permits. Expect to see more pull requests and I will include commit comments that explain the changes. Also, pull requests are welcome.

--

--

Daniel Tartaglia

I started programming as a hobby in the late ’70s and professionally in the late ’90s. I’ve been writing iOS apps since 2010 and using RxSwift since 2015.