Brent Simmons has a post about his quest to find a solution for blocking on object init while maintaining a synchronous API, and another one about how for most cross-object execution, it makes sense to stay on the main thread and only put background work on the background queue.
I haven’t had to solve the particular problem from his first post, but in a project that I’m working on, I deal with a significant amount of asynchronous execution. It mostly comes from two sources.
First, data. It comes from network, and thus it may or may not be there. Or, it may be there, but not cached/preprocessed in a correct way. So, if someObject deals with the above kind of data, and you have a call like…
… there really are two paths here. If the data is there, doSomething may return immediately in the same runloop, and execution continues without a hitch. On the other hand, if the data is not there, execution may block for an indeterminate amount of time.
The other case is chaining animations. For a professional UI, it is not uncommon for multiple things to happen after some user input. For example, if you tap a button, it might trigger the following sequence of events:
- One-shot button animation as feedback to user
- Objects moving around in the UI (added, removed, shifted around)
- Each of the added objects animating themselves
- The object signaling some other object to dismiss a modal view that it was presented in
Running them all at the same time looks like a visual explosion and it is hard for the user to follow what’s happening. Correctly orchestrating such things is a strong sign of good UI polish.
Sometimes I’ve seen these animations chained by timing: the engineer calculates how long each animation takes, and delays the next one by that amount. Not only is this brittle and looks ugly in code, but it may introduce crash bugs, as the context may change during execution: the objects may go away without knowing it themselves, and happily try to run animation when it’s no longer valid to do so.
The solution to both of these things is to use block-based APIs. A strong indication to me that this is the right thing to do is that completion blocks keep popping up left and right in all of Apple’s new APIs. Some of the key ones like
presentViewController:animated:completion: have been around for a while, but the trend continues with, say, NSURLSession that has completion handlers as blocks, unlike NSURLConnection that’s based on callbacks (and was, of course, designed long before blocks were a language feature to begin with).
Coming back to the above example, my code has API’s like this all over the place:
The example from above would thus work better like this:
1 2 3 4 5 6
A few things to note here.
First, note how we put up a progress indicator, and hide it in the completion block. In the “data available immediately” case, it’s shown and hidden in the same runloop, so it’s like it never happened in the UI. For “data available later” case, it’s immediately shown, and then hidden when appropriate.
Second, the weak self reference. It is absolutely essential to understand the strong and weak business when working with ARC, with code like this that you request to be executed, but have no control over its final scheduling or any idea when it actually gets run. Nor should you care, and if you set up the weak references correctly, you don’t. The block in this example may get run immediately. It may get run as part of a completion block of an animation or download. The target object may strongly retain it and run it after a hardcoded timer. It doesn’t matter. What matters is that your object may go away before the block gets run. When the block contains strong references to the object, it interferes with the normal expected execution flow and the object is held on to longer than you think, unless this really is the desired behavior (but most of the time with API-s like this, it isn’t). Strong references here may result in unexplained behavior, crashes, leaks, and who knows what else. If, on the other hand, you have a weak reference here, and your object happens to be released, then by the time execution gets to the block, weakSelf is treated as nil, and calling methods on nil is harmless. Note that you didn’t have to tear down the block or do anything special with it when your object is released.
Third, note the property usage. This didn’t just randomly appear in the example. If you were to manipulate any of the object’s data like variables in a block like this, the most straightforward thing to do is to make them properties. Were you to use an ivar directly in the block, it would create an implicit strong reference to self, and your memory management is again screwed. Accessing weakSelf properties, on the other hand, is fine.
An often-critiqued similar API is
NSNotificationCenter addObserverForName:object:queue:usingBlock:. I think it’s a great API for running simple code triggered by notifications, and I have never myself had a single memory management problem with it, when I’ve stuck to the weak reference rules. It sounds an awful lot like people are blaming the tools for not being able to implement things correctly themselves. Sure, tools could be better to warn us about implicit strong references. Clang sometimes does this with warnings, and sometimes as static analyzer result. But all the tools are there today to do this correctly.
On the callee/target side, one thing to note is that the block may be nil, and if you then try to call it, it crashes. Just check for its validity. Other than that, nothing special, so this kind of nesting is pretty common for me these days:
1 2 3 4 5 6 7 8 9 10
(And yes. I did have to go to fuckingblocksyntax.com to see how to correctly write the method parameter.)
The moral in the end? It’s great to be working on iOS and Mac these days if you don’t have to go back in history and can use modern block-based APIs. Blocks and ARC together make it powerful, fun, and safe to deeply nest and chain execution while maintaining clean separation and tight API-s between objects.