Integrating RxSwift Into Your Brain and Code Base: Part 2

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.

Simplifying the ProgressView

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

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.

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)

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.

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.

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.

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.

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.

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.

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…

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.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Daniel Tartaglia

Daniel Tartaglia

175 Followers

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.