Difference between revisions of "Singleton pattern"

From iPhone Development Wiki
Jump to: navigation, search
m
(It’s not immediately clear which of these is the one to use; put the correct one first. Add Swift.)
 
(3 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 +
The modern approach to creating a singleton class is the following:
 +
 +
<source lang="objc">
 +
+ (instancetype)sharedInstance {
 +
  static dispatch_once_t onceToken;
 +
  static XXXClass *sharedInstance;
 +
  dispatch_once(&onceToken, ^{
 +
    sharedInstance = [[self alloc] init];
 +
  });
 +
  return sharedInstance;
 +
}
 +
</source>
 +
 +
Or in Swift:
 +
 +
<source lang="swift">
 +
static let shared = XXXClass()
 +
 +
// Hides the init() method from other classes to ensure XXXClass.shared is always used.
 +
private init() {}
 +
</source>
 +
 +
Note that <code>static let</code> are lazy-loaded, so the implementation is identical to the Objective-C example.
 +
 +
== Other Methods (Discouraged) ==
 
The traditional singleton creation
 
The traditional singleton creation
 
<source lang="objc">
 
<source lang="objc">
Line 21: Line 46:
 
   return XXXClass_sharedInst;
 
   return XXXClass_sharedInst;
 
}
 
}
// as of iOS4, you could also use libdispatch, for example:
 
+ (XXXClass *)sharedInstance {
 
  static dispatch_once_t token = 0;
 
  dispatch_once(&token, ^{
 
    _sharedObject = [[MyClass alloc] init];
 
  }); 
 
 
</source>
 
</source>
 
<tt>pthread_once()</tt> is implemented using a spin lock (which leads to a <tt>syscall_thread_switch()</tt> kernel call in the worst case).
 
<tt>pthread_once()</tt> is implemented using a spin lock (which leads to a <tt>syscall_thread_switch()</tt> kernel call in the worst case).

Latest revision as of 04:05, 29 May 2021

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