Deleted scenes from my Swift Summit talk08 Nov 2015
Last week, I gave a talk at Swift Summit in San Francisco wherein I live coded a minimalistic Futures implementation based on BrightFutures. It was my first conference talk, but I think it went well and it was a lot of fun to do.
While preparing for the talk it became clear that the biggest challenge would be to fit my story in the assigned 18-minute slot. I ended up cutting half of the content. The result was a to the point, fast-paced, concise presentation; it was probably for the best.
However, of the content that I had to remove from the talk there’s one particular part that I regret removing most: the part that showed how to elegantly specify the thread where a callback should be called on. This is important because some stuff can only be done on the main thread, while other logic should be kept off of the main thread in order to keep your app responsive.
The following video was recorded during a practice run of my talk for my co-workers at Highstreet & Touchwonders a week prior to the conference. The remainder of this post is a loose transcription of that video, in case you prefer that.
The video starts at the point in my presentation where I’ve created a basic implementation of Async: a simple alternative for completion handlers. (It is similar to the one I used in the final version of my talk.) It represents the result of an asynchronous operation and allows for the registration of completion callbacks that will be invoked with the result of the operation when it has completed.
In the sample app, I have an asynchronous operation that fetches a list of birds and their images from the internet. Once the operation is complete, the data is stored in the view controller and
reloadData is called on the table view:
Running the app however reveals a bug: the contents of the table view only appears after the user tries to scroll. These kind of things tend to happen when calling UIKit from a background thread and in this particular case, that is exactly what is happening. The operation,
dataSource.getBirds(), uses data returned from
NSURLSessionDataTasks and eventually completes the
Async, which calls the
onComplete closure, without switching threads.
reloadData is therefore called on the thread that
NSURLSession called back on, which is not the main thread.
To solve this issue, we need a way to specify where the callback should be invoked on, i.e. a specific thread or queue, when we register the callback. As an added bonus, we gain a huge advantage over the traditional completion block based API because it will enable the caller to specify the thread the callback should be invoked on instead of leaving that up to the implementation of the asynchronous operation.
In order to properly abstract the details of threads and queues, I introduced the concept of ‘execution contexts’, something I learned from Scala. An execution context can execute logic, on a specific thread (pool) or queue. In Swift, we can define an execution context as a function that takes a closure, which is the task to execute:
With that in place we can start creating useful contexts. By creating an extension on
dispatch_queue_t we can give every GCD queue an execution context:
One context that will be particularly useful when building apps is one that performs tasks on the main queue. This is now as easy as getting
context from the main queue:
Another useful context just immediately executes a task without switching threads:
Using Execution Contexts
We can now update
onComplete to take an execution context as the first parameter. If no explicit context is specified it makes sense to use the immediate execution context.
In order to remember which callback should be invoked on which context we can simply create a new callback that wraps the given callback and executes it on the given context. That wrapped callback is then either executed immediately (if the operation is already completed) or appended to the array with pending callbacks:
Now as the final step, to fix the threading issue with the
reloadData, we can pass the
MainExecutionContext when registering the callback in the view controller:
This fixes the threading issue in what I think is a nice way: it is simple, composable and powered by Swift.
This is exactly how threading is implemented in BrightFutures. If you like it, give BrightFutures a try.