This is a repost from my old blog.
While I was at Apple’s Cocoa Camp over summer, one of the speakers talked rather passionately about the proper, thread-safe way to implement a singleton in iOS, which I thought deserves more attention than it currently does on the Web.
The Common Way
According to the first result on my Google search, a singleton should be implemented as follows:
static SingletonClass *sharedInstance = nil;
// Get the shared instance and create it if necessary.
+ (SingletonClass*)sharedInstance {
if (sharedInstance == nil) {
sharedInstance = [[super allocWithZone:NULL] init];
}
return sharedInstance;
}
Although this method is quick and easy, it is not thread-safe and the object constructor might be called multiple times if several threads access the method concurrently. We need a way to lock the constructor and insist that it be executed at most once. This is where Grand Central Dispatch comes in.
The ~Troublesome~ Thread-safe Way
GCD provides a neat API that makes concurrency programming in Cocoa convenient. To implement a thread-safe singleton in Cocoa, all you need to do is the following:
static MyObject *singleton = nil;
+(MyObject *) sharedInstance {
NSLog (@"sharedInstance called.");
if (nil != singleton) return singleton;
static dispatch_once_t pred; // lock
dispatch_once(&pred, ^{ // this code is at most once
singleton = [[MyObject alloc] init];
});
return singleton;
}
Let’s look at the code in detail. First, notice that the first few lines are
the same: we have a static variable for our singleton, and we check whether it
is nil whenever we access sharedInstance
. If singleton
is nil
, allocate and
initialize it; otherwise, return it.
The real difference is where we use the dispatch_*
types and functions. You may
think of dispatch_once_t
as a type of lock that dispatch_once()
uses to ensure
that the code is executed at most once. Here, we wrap up whatever code we wish
to be executed during the initialization of our singleton in a block. We then
pass the block as an argument to dispatch_once
. (Here's a pretty good tutorial
on blocks.)
This implementation ensures that singleton is never executed more than once
even when multiple threads access sharedInstance
concurrently:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0U);
dispatch_apply(10, queue, ^(size_t i){
[MyObject sharedInstance];
});
The above code results in the following output:
2011-09-05 10:12:05.961 SingletonTest[1602:10c03] sharedInstance called.
2011-09-05 10:12:05.961 SingletonTest[1602:12603] sharedInstance called.
2011-09-05 10:12:05.961 SingletonTest[1602:ef03] sharedInstance called.
2011-09-05 10:12:05.961 SingletonTest[1602:12703] sharedInstance called.
2011-09-05 10:12:05.966 SingletonTest[1602:10c03] init called.
2011-09-05 10:12:05.966 SingletonTest[1602:ef03] sharedInstance called.
2011-09-05 10:12:05.966 SingletonTest[1602:10c03] sharedInstance called.
2011-09-05 10:12:05.966 SingletonTest[1602:12703] sharedInstance called.
2011-09-05 10:12:05.966 SingletonTest[1602:12603] sharedInstance called.
2011-09-05 10:12:05.969 SingletonTest[1602:10c03] sharedInstance called.
2011-09-05 10:12:05.969 SingletonTest[1602:ef03] sharedInstance called.