making a class "thread safe" in iOS making a class "thread safe" in iOS objective-c objective-c

making a class "thread safe" in iOS


The easiest and best way to make a class thread safe is to make it immutable. Then you don't have to deal with any of this. It just works. It really is worth your time to think about whether you need mutability on multiple threads.

But if an immutable class creates significant problems for your design, then generally the best way to implement it is with GCD rather than locks. GCD has much lower overhead and is generally speaking easier to get right.

In this particular case, I'd implement it along these lines (untested, and I've been working in Swift for a while now, so forgive me if I drop a semicolon):

#import "SafeQueue.h"@interface SafeQueue()@property (strong, nonatomic) NSMutableArray *data;@property (strong, nonatomic) dispatch_queue_t dataQueue;@end@implementation SafeQueue- (instancetype)init {    if (self = [super init]) {        _dataQueue = dispatch_queue_create("SafeQueue.data", DISPATCH_QUEUE_CONCURRENT);    }    return self;}- (id)peek {    __block id result = nil;    dispatch_sync(self.dataQueue, ^{ result = [self.data firstObject] });    return result;}- (NSUInteger)length {    __block NSUInteger result = 0;    dispatch_sync(self.dataQueue, ^{ result = [self.data count] });    return result;}- (void)enqueue:(id)datum {    dispatch_barrier_async(self.dataQueue, ^{ [self.data addObject:datum] });}// other methods omitted...@end

Note the use of dispatch_sync for all readers and dispatch_barrier_async for all writers. This is how you keep your overhead to a minimum by allowing parallel readers and exclusive writers. And if there is no contention (which is the normal case), dispatch_sync has much lower overhead than a lock (NSLock or @synchronized or even a pthreads lock).

See Migrating Away from Threads for more advice from Apple on how to better deal with concurrency in Cocoa.


Bottom line, the first step to thread-safety is to ensure that you don't have one thread mutating the object (with it in a possibly inconsistent state) while you're trying to access it from another. So any of a variety of synchronization techniques can be useful. See Synchronization section of the Threading Programming Guide for more information on a variety of types of mechanisms. The reader-writer pattern illustrated by Rob Napier is far more efficient than the @synchronized directive or NSLock and is discussed in WWDC 2012 video Asynchronous Design Patterns with Blocks, GCD, and XPC.

By the way, the peek and length methods have diminished utility in multi-threaded environment. These suggest a dependency that the naive developer might incorrectly infer between these methods and other methods. For example, just because length is greater than zero doesn't mean that when you subsequently go to retrieve it, that anything will be there.

I would take a hard look at those methods and ask yourself whether they make sense in the multithreaded environment. I know you probably just meant these are arbitrary examples of thread-safety in mutable arrays, but it's indicative of a broader problem I see frequently in "thread-safe" examples I've seen elsewhere on Stack Overflow, where the synchronization mechanism is often at the wrong level to bear any utility.


Thread safety means that the data structure can be accessed and/or modified by multiple threads without becoming corrupt.

One simple approach is to use Objective-C's @synchronized capability.

In this case, @synchronized(self.data) around all of your accesses to the array will ensure that only a single thread can access the array at a time.

Even though length doesn't modify the array, you still need to protect its access because another thread could potentially modify the array -

#import "SafeQueue.h"@interface SafeQueue()@property (strong, nonatomic) NSMutableArray *data;@end@implementation SafeQueue- (id)peek {    @synchronized (self.data) {        return [self.data firstObject];    }}- (NSUInteger)length {    @synchronized(self.data) {        return [self.data count];    }}- (void)enqueue:(id)datum {    @synchronized(self.data) {        [self.data addObject:datum];    }}// other methods omitted...@end