Creating a Simple Reactive Table View

 

Introduction

You’d be hard-pressed to build an app these days that doesn’t need at least one table view (or collection view, but that’s for another time). So in this post, I will go over how to create a simple reactive table view using RxSwift and RxCocoa.

If you’re new to Rx, I highly encourage you to head over to RxSwift’s home page and read the Getting Started guide, download the repo, and review the example code in the Rx.playground and RxExample project. These are excellent resources!

Getting started

I’ll start off with a single-view app, in which I’ve installed RxSwift and RxCocoa via CocoaPods, and added a table view with a prototype table view cell.

You can clone the demo project from the GitHub project badge at the top and follow along by checking out the specified branch. You can check out a branch in Xcode by selecting Source Control > CapturingReferenceTypes > Switch Branch… from the menu. Because Interface Builder files may automatically be modified by Xcode simply by selecting them, you may need to select a non-Interface Builder file before switching to a different branch, and then discard (or commit) changes, which you can also do from the Source Control menu item.

01_Install_Rx_and_add_table_view

Nothing special here, just a simple table view and prototype cell, with outlets added to their associated class definitions. Next, I’ll create a data model and an Rx data source, and bind the data source to the table view.

Configuring a table view using Rx

02_Implement_Rx_table_view_1

First, I created a simple data model using an enumeration to express a few survey questions.

Then, in ViewController (after adding imports of RxSwift and RxCocoa) I used that data model to create an observable sequence, assigned to the dataSource$ constant property.

Observables should generally be declared as constants, because, once assigned, elements are added to an observable sequence vs. reassigning the observable itself. Also, I have adopted the naming convention of suffixing observable sequence values with a “$” to make them stand out from “regular” values.

I’ve also created a disposeBag property, assigned to an instance of DisposeBag. DisposeBag provides a thread-safe way to dispose of subscriptions added to it when its owner is about to be deallocated. Any time you are creating a subscription in Rx, you should generally add it to a dispose bag. And if you forget, the compiler will warn you:

02_Implement_Rx_table_view_2

This warning indicates that the result of bindNext(_:curriedArgument:) is unused. bindNext(_:curriedArgument:), as do all subscribing methods in Rx, returns a Disposable instance. I could have assigned this result to a local variable, and then added it to the dispose bag, or even manually disposed of that subscription by calling dispose() on it. However, the typical, and preferred, way to work with subscriptions is to add them to a dispose bag upon creation.

Believe it or not, that’s it!

02_Implement_Rx_table_view_3

For a simple table view, all you have to do is call rx_itemsWithCellIdentifier(_:cellType:) on a suitable data source observable to configure it. rx_itemsWithCellIdentifier(_:cellType:) takes care of the required UITableViewDataSource protocol methods, that is,
tableView(_:numberOfRowsInSection:) and tableView(_:cellForRowAtIndexPath:). There are more advanced ways to implement Rx table views that also offer additional capabilities, which I’ll cover in future posts.

The eagle-eyed of you may have noticed that I am working with a UIViewController subclass and scene, with a table view added to it, vs. a UITableViewController, which automatically includes the table view and adopts the UITableViewDataSource and UITableViewDelegate protocols. Why? Because rx_itemsWithCellIdentifier(_:cellType:) (part of RxCocoa) takes care of the required protocol methods, in addition to providing additional Rx observables and methods for many of the optional protocol methods. If I’d have used a UITableViewController and scene, even if I disconnected the delegate and dataSource outlets in Interface Builder, the class implementation would’ve required me to conform to the aforementioned UITableViewDataSource required methods, at a minimum.

But wait, there’s more…

So, what if you need to implement a protocol method for which there is not currently an Rx version? Well, you’ve got at least a couple choices:

  1. Use rx_setDelegate(_:) to essentially jump out of Rx, and then implement the protocol method in the normal manner.
  2. Implement the Rx version yourself (and if it’s good, submit a pull request and share it!).

I’ll go over how to do both of these things.

Creating a forward delegate

rx_setDelegate(_:) allows you to set a forwarding delegate, such as self, that can handle regular delegate callbacks, in conjunction with other delegate callbacks that can be handled by the Rx delegate callback methods. For example, I will use rx_setDelegate(_:) to allow the view controller to handle the UITableViewDelegate callback tableView(_:viewForHeaderInSection:).

There is currently no Rx observable or method for tableView(_:viewForHeaderInSection:). So, as an alternative to implementing one myself, I have called rx_setDelegate(_:) on the table view, to assign self as a forward delegate, and then implemented tableView(_:viewForHeaderInSection:) in an extension on ViewController that adopts UITableViewDelegate. And in the app, now a section header is displayed:

03_Use_forwarding_delegate_1

Creating a custom Rx observable

Ok, so that leaves the second option: implement a custom Rx observable. For this example, suppose that, instead of all the text fields being different widths and appearing jagged, we want them all to be the same width, equal to the space remaining based on the longest label in any of the cells.

Breaking down this feature, I need to:

  1. Determine the intrinsic label width of each cell based its text.
  2. Set the width of the label in each cell to the width of the widest label of any cell.

The table view cell is already set up with the label and text field embedded in a horizontal stack view with distribution set to Fill, meaning that views will be tend to be stretched to fill the available horizontal space (depending also on manually-added constraints and/or content hugging priorities). I’ve set the label’s horizontal content hugging priority to 1000, meaning that it will not be stretched to fill available space. The text field’s horizontal content hugging priority is 250 (the default), meaning that the text field will allow itself to be stretched horizontally to fill the available space (with regards to the label). These settings can be viewed and changed in the Size inspector in Interface Builder. The result is, the text field will stretch to fill the available space:

04_Create_custom_Rx_observable_1

If you’re not familiar with using stack views, check out my course on lynda.com: iOS UI Development with Visual Tools

iOS UI Development with Visual Tools with Scott Gardner - Customize a designable user interface

To implement this feature, I’ll start by creating a width constraint on the label:

I’ve also created a disposeBag variable property on the cell; I’ll explain why shortly.

Now I’ll set that constraint’s constant to that of the widest label based on its text in the dataSource$ binding in ViewController.

I’ve created a labelWidthConstraintConstant$ Variable with an initial value of 0.0. In the dataSource$ binding, I call sizeToFit() on the questionLabel after assigning its text, to get its intrinsic width, and then I add to labelWidthConstraintConstant$ the max of its current value and the width of the label. Then I use labelWidthConstraintConstant$ to drive the cell’s questionLabelWidthConstraint.rx_constant value (rx_constant is an observable of the constant property of a constraint, aka a bindable sink).

Driver is a special kind of observable sequence that is ideal for use with binding to UI elements. Among other useful features, a Driver will never fail, always deliver events on the main thread, and it will replay the last value when initially subscribed.

drive(_:) creates a subscription within the cell. Remember that table view cells are not deallocated during the lifecycle of the table view. Once allocated, they’re removed from the table view and added to the queue, waiting in turn to be dequeued when the table view needs to display another cell. Because of this, I’ll need to add the subscription to a dispose bag on the cell itself, and then empty that dispose bag after it is removed from the table view. If I didn’t do that, subscriptions would keep being added to the same cell, even as it’s dequeued, used, removed (but not deallocated), dequeued again, etc. And because of this need, I will implement a custom Rx observable to handle UITableViewDelegate‘s
tableView(_:didEndDisplayingCell:forRowAtIndexPath:) callback.

I’ve implemented rx_didEndDisplayingCell as a computed property on UITableView in an extension. In its getter, I construct an observable source$ of type tuple of UITableViewCell and NSIndexPath, by calling observe on rx_delegate (which is an Rx wrapper around the delegate) and passing the selector for the delegate method I want to listen for. Then I create and return a ControlEvent that wraps that source$ observable in a UI event listener on the main thread.

I can now use this Rx observable to listen for the tableView(_:didEndDisplayingCell:forRowAtIndexPath:) callback and then properly dispose of the subscription, by simply setting the cell’s disposeBag variable property to a new instance of DisposeBag. Doing so will cause dispose() to be called on its contents.

In the app, the table view cells’ labels are all aligned nicely and their text fields are the same width:

04_Create_custom_Rx_observable_2

Wrap up

Creating a simple reactive table view using RxSwift and RxCocoa is quick and easy, and there are options for when your needs go beyond the basics. I’ve only scratched the surface here, but stay tuned! I plan to go into more depth and also cover RxDataSources in future posts.

I hope you enjoyed this post. Nothing says “thanks” like a share. Cheers!

Capturing Reference Types

 

This being a blog about reactive programming with RxSwift (and friends)–which rely heavily on asynchronicity and closures–I’d like to kick things off by covering one of the most commonly misunderstood things about writing asynchronous code in Swift: how to properly work with reference types in closures.

Fortunately, the rules of engagement are pretty straightforward. Follow them and you should be fine. Ignore them and, well, bad things can happen.

I’ll start with a simple example.

You can clone the demo project from the GitHub project badge at the top and follow along by checking out the specified branch. You can check out a branch in Xcode by selecting Source Control > CapturingReferenceTypes > Switch Branch… from the menu. Because Interface Builder files may automatically be modified by Xcode simply by selecting them, you may need to select a non-Interface Builder file before switching to a different branch, and then discard (or commit) changes, which you can also do from the Source Control menu item.

01_Simple_Example_1

In handleRefreshButtonTapped(_:), I create a random color and then assign it to the view‘s backgroundColor in UIView.animateWithDuration(_:animations:)‘s animations closure. In the app, each time the refresh button is tapped, the view’s background color will change via a short animation:

01_Simple_Example_2

By accessing self in the animations closure, I am capturing self (aka closing over self). Notice that I’ve explicitly written self in the closure. This is required by the compiler, to make sure I’m aware that self is being captured in the closure. If I omitted it, I’d get an error:

01_Simple_Example_3

When a closure captures a reference type, by default it creates a strong reference to that reference type. And if that captured reference type also has a strong reference to the closure, a strong reference cycle is created between the two reference types, and neither can be deallocated. The result is a memory leak.

Capturing self is harmless in this simple example, because the view controller does not ever get deallocated (it’s a single-view app). But in a more complex app, this could cause a memory leak or even a crash. To demonstrate how that could happen, I’ll modify the example project.

Summary of changes:

  1. Installed CocoaPods, a fine dependency manager.
  2. Installed Async, a lightweight wrapper around GCD.
  3. Installed RxSwift and RxCocoa, the official reactive extensions for Swift and Cocoa.
  4. Rx-ified the project. More on this in a moment.
  5. The background-color-changing view controller is now being presented in a popover.
  6. Added a 5-second delay before changing the background color after tapping the refresh button.

02_Rx-ify_Simple_Example_1

I’m going to go over the ins and outs of RxSwift, RxCocoa, and additional Rx libraries in future posts. If you’re new to Rx, though, I highly encourage you to head over to RxSwift’s home page and read the Getting Started guide, download the repo, and review the example code in the Rx.playground and RxExample project. These are excellent resources!

In viewDidLoad, I am now binding tap events on the refreshButton to a closure that does the same thing as was previously being done in handleRefreshButtonTapped(_:), except now the animation block is nested in an Async block that delays its execution by 5 seconds. I’ve also added a couple print statements so that I can see when the animations closure is executed, and when ViewController is about to be deallocated.

Notice that I am capturing self in the closure parameter to bindNext(_:) (in addition to the animations closure, as before).

In the app, I will first tap the refresh button and wait for the animation to complete before tapping Done to dismiss ViewController, and then I’ll tap the refresh button and Done immediately after, without waiting for the animation to complete. In both cases, ViewController never gets deallocated, evidenced by the fact that the print statement in deint never executes:

02_Rx-ify_Simple_Example_2

The reason ViewController never gets deallocated is because it has a strong reference to the closure parameter of bindNext(_:), and that closure captures and holds a strong reference to the ViewController. It’s deadlock, and that memory is leaked. If there were enough of these strong reference cycles occurring in an app, the memory pressure would eventually cause the app to be killed.

What I need to do is define a capture list. A capture list defines rules for how to capture one or more reference types in the closure, as follows:

If it is possible that the capture can become nil (such as by being deallocated) before closure is executed, define the capture as weak. If not, that is, the capture and the closure will always be deallocated at the same time, define the capture as unowned.

So now I have to decide whether to define a weak or unowned capture of self.

Both weak and unowned captures will not prevent ARC from disposing of the capture if they are the last reference to that capture. The difference between the two is that weak can be set to nil (and, thus, must be an optional), and unowned cannot.

The syntax to define a capture list is to enclose a rule (or multiple rules in a comma-separated list) inside square brackets, within the closure body, after the opening curly brace and before the closure’s parameter list and return value (if provided, and cannot be inferred), followed by the in keyword. A rule consists of the weak or unowned keyword followed by a single capture.

I have modified the bindNext(_:) call to define an unowned capture of self. In the app, if I tap the refresh button and wait for the animation to complete before tapping Done, ViewController is properly deallocated, the capture of self is released, and all is good. But, what happens if I do not wait for the animation to complete, and instead tap the refresh button immediately followed by Done? ViewController is deallocated (self becomes nil), but the capture of self in the closure cannot be set to nil, because we defined it as unowned, which is non-optional. The closure will not be released until it is executed. So, when the closure is executed and attempts to access its self capture, an EXC_BAD_ACCESS exception is thrown (technically, swift_unknownUnownedTakeStrong()):

03_Example_unowned_capture_1

Clearly, unowned is not the right choice here, because the capture can become nil before the closure is executed. I must use a weak capture.

I have changed the capture to weak, which makes it an optional. Then, at the top, I used a guard statement to unwrap the capture and assign it to a local self constant if it is not nil, or else simply return if it is nil. Thanks to Marin Todorov for sharing the 'self' idea (source)! That way, I am able to still reference self within the scope of bindNext(_:), but self is now referring to the local capture. And now all is good:

04_Example_weak_capture_1

I hope you enjoyed this article. Nothing says “thanks” like a share. Cheers!