Singleton pattern

From iPhone Development Wiki
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

The modern approach to creating a singleton class is the following:

+ (instancetype)sharedInstance {
  static dispatch_once_t onceToken;
  static XXXClass *sharedInstance;
  dispatch_once(&onceToken, ^{
    sharedInstance = [[self alloc] init];
  });
  return sharedInstance;
}

Or in Swift:

static let shared = XXXClass()

// Hides the init() method from other classes to ensure XXXClass.shared is always used.
private init() {}

Note that static let are lazy-loaded, so the implementation is identical to the Objective-C example.

Other Methods (Discouraged)

The traditional singleton creation

+ (XXXClass *)sharedInstance {
  static XXXClass* XXXClass_sharedInst = nil;
  @synchronized(self) {
    if (XXXClass_sharedInst == nil) {
      XXXInitialize(&XXXClass_sharedInst);
    } 
  }
  return __sharedInst;
}

is extremely inefficient because @synchronized not only lock a mutex, but also insert an exception handler (try/catch block). If you don't care about exceptions and willing to use C functions, pthread_once() is a better alternative:

static XXXClass *XXXClass_sharedInst = nil;
static pthread_once_t XXXClass_onceControl = PTHREAD_ONCE_INIT;
static void XXXInitializeOnce(void) { XXXInitialize(&XXXClass_sharedInst); }
...
+ (XXXClass *)sharedInstance {
  pthread_once(&XXXClass_onceControl, &XXXInitializeOnce);
  return XXXClass_sharedInst;
}

pthread_once() is implemented using a spin lock (which leads to a syscall_thread_switch() kernel call in the worst case).

If it is safe to create multiple copies of the singleton and destroy the extra ones, you may even use CAS:

+ (XXXClass *)sharedInstance {
  static XXXClass* XXXClass_sharedInst = nil;
  if (XXXClass_sharedInst == nil) {
    XXXClass* tmp;
    XXXInitialize(&tmp);
    if (!OSAtomicCompareAndSwapPtrBarrier(nil, tmp, (void*volatile*)&XXXClass_sharedInst)))
      XXXDestroy(tmp);
  }
  return XXXClass_sharedInst;
}

Unfortunately, OSAtomicCompareAndSwapPtrBarrier() on ARMv4 is implemented using a global spin lock, so in principle this is nowhere faster than pthread_once(), and is even more error-prone. On ARMv6 it is implemented as a LDREX/STREX pair[1]:

ENTRY_POINT(_OSAtomicCompareAndSwapPtrBarrier)
1:	ldrex	r3, [r2]	// load existing value and tag memory
	teq	r3, r0		// is it the same as oldValue?
	movne	r0, #0		// if not, return 0 immediately
	bxne	lr	    
	strex	r3, r1, [r2]	// otherwise, try to store new value
	cmp	r3, #0		// check if the store succeeded
	bne	1b		// if not, try again
	mov	r0, #1		// return true
	bx	lr

References