Menu

Make your UIViewController Awesynchronous!

Welcome back for the conclusion of this 3 part series! In part 1 we learnt how to slim down our view controllers to a manageable size. In part 2 we learnt how to strip out all the non-ui related logic. This time we are going to focus solely on the UITableView and how we can properly handle multiple asynchronous operations in its cells for maximum performance!

...and you would be right! kinda... This would definitely work, in fact if you change the code and run it now the scrolling would be fantastic! There's no image caching to stop us downloading the images over and over but we can fix that with something like SDWebImage right?

Sadly this is what the majority of projects will do, but this is not the way to do things.

Why is this bad?

The problem lies in the lifecycle of a UITableViewCell. You have no way of knowing for sure how long a cell will exist for. As soon as it's scrolled off the page it will be marked for recycling and potentially reused for the cell that is now coming on screen. This makes our code inconsistent and we want to avoid that at all costs!

This issue becomes even worse when you add things like really fast user scrolling, more expensive async operations or a combination of the two!

Best case scenario is nothing goes wrong, but you're probably wasting a lot of resources running async operations over and over that may or may not get a chance to complete and cache. Worst case is you start getting random EXC_BAD_ACCESS errors that you will tear your hair out trying to debug.

For these reasons, much like our view controller, we should be aiming to make our cells as dumb as possible. They should be a simple view that takes simple values.

Great!... but some of my data doesn't live on my device and I NEED to grab it asynchronously.

Not a problem, I am going to show you how you can introduce consistency and predictability back into your code by managing async operations in a way that decouples them from the lifecycle of your UITableViewCell allowing them to complete, even if your cell gets recycled. This way, with some simple caching, the data will be ready for the cell next time it's shown giving that buttery smooth interface we all love to use!

A better way

We are going to introduce two new classes to help us manage these operations. Lets take a look!

We will pass in our async operation via the constructor. Calling execute will run the block on a background thread. Upon successful completion of the operation the result will be cached in value, once this happens any more calls to execute will do nothing as we already have the value.

A new concept for some here might be the tableOperationResultBlock block. This block in turn provides two blocks as its parameters that can be called depending on the success or failure of the operation. Don't worry if you don't understand just yet, we will see it in practice soon.

TableOperation objects are passed to addOperationIfNeeded:indexPath and if no operation exists for the given indexPath they will be stored. The collection of operations can be cleaned up using invalidate.

As our datasource/delegate builds and shows cells it can make calls to getDataForCellAtIndexPath: to execute operations if needed, the results will then be delivered via the didGetDataForCell block on the main thread.

The implementations for these two classes are very simple and can be found here. Go grab them and add them to our project before continuing.

Freeing our cells from their burden

To use our new classes we will first create a category on our VCTableCellData class. It will give us a TableOperation for that specific VCTableCellData object.

This operation will perform the image download for a row in our table on a background thread. It then calls either the success block along with the image or failure with an NSError. This is the technique described earlier which, by passing through blocks as parameters, allows the flow of code to continue on the path we need it to.

Next we will update our cells so they don't need to worry about async stuff anymore. Add the following method to the header of TableCell

This code will ensure our VCTableCoordinator is now notified when our async operations complete. It will then look for the related UITableViewCell and, if it is visible, pass the image through so it can be displayed.

This will ensure that, only when needed, each cell's operation is added to our TableOperationsManager and that it is executed.

That's Awesynchronous!

Go ahead and edit the title method in VCTableModel to make it say "Async Table View Example" to let the other view controllers know that this one is better! ;)

NOTE:It's important to note that the design shown in this article doesn't only apply to the table view. I highly recommend something like this for collection views and any other container style views whose 'cells' have an undetermined lifespan

Fire up the project and enjoy the buttery goodness! You can find the full source here

That's it for this series! I hope you've enjoyed it and hopefully learnt something new!