As most battle-hardened programmers will attest, implementing threaded code that reads or writes to shared mutable state is hard to develop correctly and rapidly degenerates into a maintenance nightmare unless all programmers are equally skilled. Lets review why; shared mutable state requires locks and:

  • Locks do not compose
  • Locks break encapsulation (you need to know a lot!)
  • Error recovery is hard
  • Its easy to get locks wrong by:
    • Taking too few locks
    • Taking too many locks
    • Taking the wrong locks
    • Taking locks in wrong order

In summary unless handled with extreme caution, threaded code tends to lead to impossible to reproduce bugs - it’s non deterministic - you only find out there’s a problem when you see the crash reports.

I’ve found the way to deal with threads in iOS is to try really hard to avoid them.

Eliminating threads is relatively easy for simple apps, as the networking APIs provide asynchronous callbacks, though behind the scenes the network stack is using threads. I initiate a network request on the main thread and then get called-back on the main-thread with the result.

In more complex apps, I wrap-up threaded background work in a similar manner and provide asynchronous callbacks.

Futures and promises

Futures and Promises are a great alternative to completion blocks and support typesafe error handling in asynchronous code. Using Futures for all asynchronous calls makes explicit the asynchronous nature of an API and allows the asynchronous code to compose in ways that are unimaginable with callback blocks.

Here’s an example of the use of Futures taken from iDiffView

func readSample() -> Future<(DiffTextDocument, DiffTextDocument), DiffTextDocumentErrors> {
      func readSampleText(assetName: String) -> Future<DiffTextDocument, DiffTextDocumentErrors>  {
        let url = NSBundle.mainBundle().URLForResource(assetName, withExtension: "txt")!
        let document = DiffTextDocument(fileURL: url)
        return document.open()
      }
      return readSampleText("Sample1Left").zip(readSampleText("Sample1Right"))
}

The method readSample() returns a two element tuple representing the left and right sample documents, wrapped in a Future. The nested function readSampleText(String) returns a document wrapped in Future. The compositional magic occurs in the line:

return readSampleText("Sample1Left").zip(readSampleText("Sample1Right"))

which uses zip to compose the two Futures returning a single Future wrapping a two element result tuple.

Prior to using Futures the above line of code was written using chained closures as:

readSampleText("Sample1Left") { (document1) in		
    readSampleText("Sample1Right") { (document2) in		
        callback(left: document1, right:document2)		
    }		
}

Clearly callbacks don’t compose in the same way as Futures and the Future based code also provides an error path, which is absent from the callback example.

Show activity indicator while busy.

It’s a good idea to display an activity indicator to the user, if they are unable to use the UI until an asynchronous callback completes.

With a Future this is a single line futureResult.showActivityIndicatorWhileWaiting():

func loadInitialText() {
  let futureResult = readSample().showActivityIndicatorWhileWaiting(onView: self.view)
  futureResult.onSuccess { (leftDocument, rightDocument) in
      .
      .   
  }
}

The activity indicator is displayed until the Future completes; whether successfully or in failure state.

Compare this to the original pre-future version, where you have to remember to manually remove the activityOverlay once the completion block is called:

func loadInitialText() {
  let activityOverlay = showActivityOverlayAddedTo(self.view)
  readSample { (leftDocument, rightDocument) in
    activityOverlay.removeFromSuperview()
    .
    .
   }
}


showActivityIndicatorWhileWaiting() is implemented as a Future extension as:

extension Future {
    func showActivityIndicatorWhileWaiting(onView superview: UIView) -> Self {
        let activityOverlay = showActivityOverlayAddedTo(superview)
        return onComplete { _ in
            activityOverlay.removeFromSuperview()
        }
    }
}

… which shows another advantage of Futuress; the ability to associate multiple completion blocks with a Future

See also