Singleton Pattern in Objective-C

Ivan Nemes Categories: Design patterns and principles Date 15-Jul-2014
Singleton Pattern In Objective C

Table of contents

    For the singleton pattern implementation there are two important things which should be considered:

    • how will we ensure that just one instance of the class will be created (how singleton getter will look like) and
    • in which way will we provide synchronized access to the resources inside of the singleton? 

    In objective C, there are two common ways of implementing the singleton pattern. The following text illustrates how they work and what are the downsides of both. Please note that it's perfectly fine to combine singleton's getter implementation of one approach with the synchronization mechanism of the second approach and vice versa. First approach, a traditional one, includes locks and synchronized blocks to ensure singleton functionality. The second one uses GCD (Grand Central Dispatch) library and queuing support for it. Let us take a look at those two implementations by using a simple example of the singleton with just two methods in which only one public method can be executed at the moment.

    First approach, with synchronized blocks is listed below:

    @implementation SingletonClass1static SingletonClass1 * instance = nil;+ (SingletonClass1 * ) instance { @synchronized(self) { if (instance == nil) { instance = [[SingletonClass1 alloc] init]; } } return instance;}+ (id) allocWithZone: (NSZone * ) zone { @synchronized(self) { if (instance == nil) { instance = [super allocWithZone: zone]; return instance; // assignment and return on first allocation } } return nil; // on subsequent allocation attempts return nil}- (void) method1 { @synchronized(self) { NSLog(@"method 1 called..."); NSString * str = @"xxx"; for (int i = 0; i < 1000; ++i) { str = [NSString stringWithFormat: @"%@1", str]; } }}- (void) method2 { @synchronized(self) { NSLog(@"method 2 called..."); NSString * str = @"xxx"; for (int i = 0; i < 1000; ++i) { str = [NSString stringWithFormat: @"%@1", str]; } }}@end

    Synchronized blocks (locks) require trapping inside of the kernel with purpose to acquire a mutex. That's often considered as a possible performance penalty. Second approach, which uses GCD library and its queues, is listed below:

    @implementation SingletonClass2{ dispatch_queue_t _serialQueue;}+ (SingletonClass2 *)instance{ static dispatch_once_t once; static SingletonClass2 *sharedInstance; dispatch_once(&once, ^{ sharedInstance = [[self alloc] init]; }); return sharedInstance;}- (id)init{ self = [super init]; if (self != nil) { _serialQueue = dispatch_queue_create("SerialTestQueue", DISPATCH_QUEUE_SERIAL); } return self;}- (void)method1{ dispatch_sync(_serialQueue, ^ { NSLog(@"Method 1 called..."); NSString *str = @"xxx"; for (int i = 0; i < 1000; ++i) { str = [NSString stringWithFormat:@"%@1", str]; } });}- (void)method2{ dispatch_sync(_serialQueue, ^ { NSLog(@"Method 2 called..."); NSString *str = @"xxx"; for (int i = 0; i < 1000; ++i) { str = [NSString stringWithFormat:@"%@1", str]; } });}@end

    Even though, second approach is considered as a faster and safer one, there are a few things which can cause real issues and problems. Those issues are the main topic of this text and they are listed below.

    Deadlock issue

    To make a synchronized and sequential access to the shared resources of the singleton, the GCD implementation of the singleton pattern uses sequential queue. It can be dangerous when one public method calls another one. Because all public methods use a sequential queue for the synchronization, deadlock will occur when one method calls another one. To be more precise, call of the second method will put its execution at the end of the sequential queue and that part of the code won't be executed because the first function is in execution phase. Take a look at the following image.


    Sequential queue execution (Deadlock issue)

    By using traditional approach with locks (synchronized blocks), this is harder to achieve. In that case, the second method will be executed without any problems because appropriate lock is already taken; therefore we have a simple example of execution under a nested lock.

    Exception handling issue

    Usage of the sequential queue inside of the singleton has one more downside. Entire code which is executed on the sequential queue actually works on the separated thread. It means that if something fails and exception is thrown, caller of the singleton method won't be able to catch that exception. It means that entire application can crash because of the unhandled exception. By using traditional approach of locks, execution inside of the singleton stays on the same thread that is also used by a caller, which means that any exception thrown inside of the singleton can be catch easily outside of it. So, if sequential queue is used, exception handling should be done inside of the singleton method, and error result should be gracefully provided by return result or output parameter.

    Although it's possible to implement singleton pattern with GCD library, this approach has a few traps. So, it's good to be aware of the issues mentioned in this text. Also, even though this approach with GCD library is considered as a faster one because of the lock penalties in the traditional approach, performance tests are showing that both ways of implementation have quite similar speed results.

    Ivan Nemes Software Developer

    Ivan is a perfectionist, and a competitive athlete, qualities that are reflected in his tendency to not only match industry standards, but consistently improve upon them.