Consistency Managers & Communication Channels — RocketData as a Messaging Bus

I’m a Computer Engineering student at the University of Waterloo, currently working at 500px as a mobile developer for my co-op term. This term I’ve primarily been working on the 500px iOS app, and specifically tackled the problem of consistency management. Consistency management is a common problem for mobile apps. When a user interacts with […]

I’m a Computer Engineering student at the University of Waterloo, currently working at 500px as a mobile developer for my co-op term. This term I’ve primarily been working on the 500px iOS app, and specifically tackled the problem of consistency management.

Consistency management is a common problem for mobile apps. When a user interacts with the app, they expect that their actions will be reflected consistently throughout all areas of the interface. For example, if a user deletes their photo, they expect to see this photo removed from other views of the app, such as their profile or the home feed. Achieving consistency can be problematic for the views retained in memory, where the data can become outdated. Consistency is easy to take for granted — but when it’s not there, users notice!

In 500px for Android, we use Jackie to ensure consistency. We wanted a similar solution for iOS, so we started off looking into what was already out there. After some research, we found RocketData from LinkedIn. RocketData provides similar functionality to Jackie, and both solutions were inspired by the same Facebook talk. RocketData has some nice benefits over Jackie: it handles concurrency slightly better, is explicit about what thread a function will run on, and it nicely handles most threading issues automatically. RocketData also supports disk caching, whereas Jackie only supports an in-memory cache. Overall, they both satisfy the core functionality required to solve the consistency problem.

After playing around with RocketData, we realized that it could be used as a message bus to solve communication issues between various parts of the view hierarchy. Let’s dig into the details of both approaches, and describe why we ended up with the RocketData message bus approach.

Communication Channels

A key part of consistency management is communicating state changes throughout different components of our app. When there is a change in a data model, this change needs to be propagated to other parts of the app, so that other view controllers can update their UI to reflect the change. In some older parts of our app, we use NSNotificationCenter as a message bus to call different view controllers to perform some action, and we use the delegate pattern to handle communication between UIViews and their view controllers.

Delegate Pattern

The delegate pattern is a good way of handling communication between UI views and their view controllers. However, it can create long delegate chains when you have deeply nested view hierarchies. We encountered this issue when we started using IGListKit to simplify our massive collection/table view controllers. IGListKit allowed us to decouple our UI into different components: view controller, section controller and UIViews. The view controller managed all the section controllers, and the section controllers managed the different type of cell views that we presented.

Here’s a simple example of the long delegate chain problem: the user clicks a button in the app. The button is in our UIView, which will have to call a delegate function that’s implemented in the section controller. However, the operation that we want to perform is in our view controller, i.e. dismiss the cell. Therefore, we need to make another delegate call. The section controller ends up just being a messenger between our UIView and the view controller.

Although using IGListKit made our code more modular, cleaner and easier to debug, it introduced these long delegate chains which added a lot of duplicate code and were annoying to debug. It’s also wasteful because a section controller might have to store a delegate property that it may never use, but simply passes to one of its child views. First, we considered using NSNotificationCenter as an alternative to using delegates.

NSNotificationCenter

With the NSNotificationCenter approach, we register observers for particular events, and send NSNotification events to trigger those observers. When notified, the observer calls its selector function to handle the event.

One advantage to using NSNotificationCenter is that there is no coupling, so it makes it easy to swap out code. However, the NSNotificationCenter approach has several downsides:

First, you need to make sure that you unregister all your observers or you risk having bad pointer crashes. This is no longer a problem with iOS 9 and up, but you still need to unregister observers for older iOS versions, as well as block based observers.

Second, there is the risk of deadlock: if two observers broadcast notifications, and are listening to each other’s notifications, we will deadlock. The usual fix for this is to ensure that observers aren’t broadcasting any messages, but that is error-prone and often leads to messy workarounds.

Third, NSNotificationCenter makes debugging difficult. When debugging there is no line-of-sight access between the observer and the notification sender. So if we were to place a breakpoint in the function that our observer calls, we can’t navigate the call stack to where the notification was sent to check the state of the app at that point.

Those are just a few of the common issues with using NSNotificationCenter. We’ve experienced these problems first-hand in our app in the past, so we wanted to find a better alternative.

RocketData

To use Rocket Data as a message bus, we used RocketData’s DataProvider and DataModelManager classes. From LinkedIn’s post:

“Any models that are added to a data provider become “managed” models. Rocket data will ensure that these models are kept in sync with the cache and any other data providers”.

DataModelManager and DataProvider ensure that when one part of the app updates a model, the change is automatically reflected in other parts of the app. We decided to try using this functionality as a message bus by storing event objects in the DataProvider. I’ll use one of our collection view controllers as an example.

First, we create a DataProvider in our ActivityViewController that holds a refresh event model (ActivityViewControllerEvent).

https://medium.com/media/2f9274ff7470b6ab6739d4e1cc865b5e/href

In our viewDidLoad function, we instantiate a refresh event model and pass it to the DataProvider. We also set the DataProvider’s delegate to be the ActivityViewController:

https://medium.com/media/a45475743e8cc07effb73270b55bde0f/href

Next, we implement the DataProviderDelegate protocol to handle changes to the model:

https://medium.com/media/0af6a3aff717cbddcacf7ebf09eb450f/href

When we use DataModelManager to make a change to the refresh event model that is stored in the consistency manager, it will call the DataProviderDelegate in our ActivityViewController to handle this change. In order for our DataModelManager to detect changes between the model in the consistency manager and the model that we’re passing into DataModelManager, we need to implement the == function.

https://medium.com/media/4e4dd54ec90d2d54299337f87e8600a5/href

In our case we give the ActivityViewControllerEvent a timestamp property, set to the current date when the model is created. This timestamp property is used by DataModelManager to compare the model to other models. Since the timestamp is set at creation, every model will be different. So when we use DataModelManager to update our refresh event model, it will always see that the objects are different and notify our DataProviders that there has been a change.

https://medium.com/media/c38e0692963aaa95a71ce944eb0d58e5/href

In our case, the DataProvider in ActivityViewController will be notified and will call its DataProviderDelegate to handle the change.

RocketData directly addresses the pain points from the NSNotificationCenter approach: there’s no risk of memory leaks, it’s easier to debug, and we don’t need to worry about unregistering our DataProviders. When we call updateModel from DataModelManager, the response from the DataProviderDelegates responds on the same thread, which simplifies things, and it automatically guarantees consistency.

We’d love to hear about how others have solved these problems or tried out similar solutions, so drop us a note and let us know what you think!


Consistency Managers & Communication Channels — RocketData as a Messaging Bus was originally published in 500px Engineering Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.

Source: 500px