When it comes to making things "faster", an easy thing to reach for is concurrency. GCD and NSOperations make dealing with threads a breeze and if your task lends itself to concurrency then it makes a lot of sense.

The thing to remember is, once you've introduced concurrency into a system, it's your responsibility to make sure your data stays in sync. Depending on the application, not doing so could result in hard to debug problems ranging from inconsistent UI to catastrophic data loss.

Since we want to avoid all of those things, it's best to go into multi-threading with synchronization in mind from the start.

That being said, it's also important to remember that ensuring correctness via these strategies will necessarily slow down your code. If you've decided to make a part of your app concurrent, make sure to test the performance to make sure the concurrency you've added actually makes things faster.

Strategies

Like anything, you have a couple options when it comes to keeping your code in sync. To get a feel for how usage looks, we'll look at each of these mechanisms in the context of building out a simple cache. The cache is just a wrapper around NSDictionary that makes it usable from different threads.

We'll compare how each performs while inserting 10,000 objects into the dictionary. The scores are given in average nanoseconds per insert.

One thing to take note of before we jump in is the documentation for the dispatch_benchmark function you can use to do benchmarking.

Code bound by computational bandwidth may be inferred by proportional changes in performance as concurrency is increased.

may be inferred by proportional changes in performance as concurrency is increased. Code bound by memory bandwidth may be inferred by negligible changes in performance as concurrency is increased.

may be inferred by negligible changes in performance as concurrency is increased. Code bound by critical sections may be inferred by retrograde changes in performance as concurrency is increased.

Interestingly, filling this dictionary with 10,000 elements is actually faster if we just do it serially without any multi-threading. Therefore we can be relatively sure that changes in speed we see are related to the differences in these locking strategies. To be fair, there's not a lot of other code it could be due to, but something to keep in mind for when you're looking at more complex situations.

NSLock Objects

First up is the basic NSLock object. To use an NSLock, all you need to do is make sure to initialize it with your object, and then when you want to use it to lock on a critical section, you can use the -lock and -unlock methods.