objective c - ¿Cómo debería ser mi Singleton Objective-C?
design-patterns object-initializers (26)
KLSingleton es:
- Subclase (hasta el grado n)
- Compatible con ARC
- Seguro con
alloc
yinit
- Cargado perezosamente
- A salvo de amenazas
- Sin bloqueo (usa + inicializa, no @synchronize)
- Macro-libre
- Libre de Swizzle
- Sencillo
Mi método de acceso singleton suele ser una variante de:
static MyClass *gInstance = NULL;
+ (MyClass *)instance
{
@synchronized(self)
{
if (gInstance == NULL)
gInstance = [[self alloc] init];
}
return(gInstance);
}
¿Qué podría estar haciendo para mejorar esto?
¿No debería ser seguro para subprocesos y evitar el costoso bloqueo después de la primera llamada?
+ (MySingleton*)sharedInstance
{
if (sharedInstance == nil) {
@synchronized(self) {
if (sharedInstance == nil) {
sharedInstance = [[MySingleton alloc] init];
}
}
}
return (MySingleton *)sharedInstance;
}
Con los métodos de clase C de Objective, podemos evitar el uso del patrón singleton de la forma habitual, desde:
[[Librarian sharedInstance] openLibrary]
a:
[Librarian openLibrary]
al envolver la clase dentro de otra clase que solo tiene Métodos de Clase , de esa manera no hay posibilidad de crear instancias duplicadas accidentalmente, ¡ya que no estamos creando ninguna instancia!
Escribí un blog más detallado here :)
Desde que Kendall publicó un singleton threadsafe que intenta evitar los costos de bloqueo, pensé que también lanzaría uno:
#import <libkern/OSAtomic.h>
static void * volatile sharedInstance = nil;
+ (className *) sharedInstance {
while (!sharedInstance) {
className *temp = [[self alloc] init];
if(!OSAtomicCompareAndSwapPtrBarrier(0x0, temp, &sharedInstance)) {
[temp release];
}
}
return sharedInstance;
}
Bien, déjame explicarte cómo funciona esto:
Caso rápido: en la ejecución normal, la
sharedInstance
ya se ha establecido, por lowhile
ciclo while nunca se ejecuta y la función regresa después de simplemente probar la existencia de la variable;Caso lento: si
sharedInstance
no existe, entonces se asigna una instancia y se copia en ella utilizando un comparador e intercambio (''CAS'');Caso contendido: si dos subprocesos intentan llamar a
sharedInstance
al mismo tiempo YsharedInstance
no existe al mismo tiempo, ambos inicializarán las nuevas instancias del singleton e intentarán colocarlo en su posición. Cualquiera que gane el CAS devuelve inmediatamente, el que pierde libera la instancia que acaba de asignar y devuelve la instanciasharedInstance
(ahora establecida). El únicoOSAtomicCompareAndSwapPtrBarrier
actúa como una barrera de escritura para el hilo de configuración y una barrera de lectura del hilo de prueba.
Esto funciona también en un entorno recolectado sin basura.
@interface MySingleton : NSObject {
}
+(MySingleton *)sharedManager;
@end
@implementation MySingleton
static MySingleton *sharedMySingleton = nil;
+(MySingleton*)sharedManager {
@synchronized(self) {
if (sharedMySingleton == nil) {
[[self alloc] init]; // assignment not done here
}
}
return sharedMySingleton;
}
+(id)allocWithZone:(NSZone *)zone {
@synchronized(self) {
if (sharedMySingleton == nil) {
sharedMySingleton = [super allocWithZone:zone];
return sharedMySingleton; // assignment and return on first allocation
}
}
return nil; //on subsequent allocation attempts return nil
}
-(void)dealloc {
[super dealloc];
}
-(id)copyWithZone:(NSZone *)zone {
return self;
}
-(id)retain {
return self;
}
-(unsigned)retainCount {
return UINT_MAX; //denotes an object that cannot be release
}
-(void)release {
//do nothing
}
-(id)autorelease {
return self;
}
-(id)init {
self = [super init];
sharedMySingleton = self;
//initialize here
return self;
}
@end
He incluido singleton en una clase, por lo que otras clases pueden heredar propiedades singleton.
Singleton.h:
static id sharedInstance = nil;
#define DEFINE_SHARED_INSTANCE + (id) sharedInstance { return [self sharedInstance:&sharedInstance]; } /
+ (id) allocWithZone:(NSZone *)zone { return [self allocWithZone:zone forInstance:&sharedInstance]; }
@interface Singleton : NSObject {
}
+ (id) sharedInstance;
+ (id) sharedInstance:(id*)inst;
+ (id) allocWithZone:(NSZone *)zone forInstance:(id*)inst;
@end
Singleton.m:
#import "Singleton.h"
@implementation Singleton
+ (id) sharedInstance {
return [self sharedInstance:&sharedInstance];
}
+ (id) sharedInstance:(id*)inst {
@synchronized(self)
{
if (*inst == nil)
*inst = [[self alloc] init];
}
return *inst;
}
+ (id) allocWithZone:(NSZone *)zone forInstance:(id*)inst {
@synchronized(self) {
if (*inst == nil) {
*inst = [super allocWithZone:zone];
return *inst; // assignment and return on first allocation
}
}
return nil; // on subsequent allocation attempts return nil
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX; // denotes an object that cannot be released
}
- (void)release {
//do nothing
}
- (id)autorelease {
return self;
}
@end
Y aquí hay un ejemplo de alguna clase, que quieres convertirte en singleton.
#import "Singleton.h"
@interface SomeClass : Singleton {
}
@end
@implementation SomeClass
DEFINE_SHARED_INSTANCE;
@end
La única limitación sobre la clase Singleton es que es una subclase NSObject. Pero la mayoría del tiempo que uso singletons en mi código, en realidad son subclases de NSObject, por lo que esta clase realmente me facilita la vida y hace que el código sea más limpio.
La respuesta aceptada, aunque compila, es incorrecta.
+ (MySingleton*)sharedInstance
{
@synchronized(self) <-------- self does not exist at class scope
{
if (sharedInstance == nil)
sharedInstance = [[MySingleton alloc] init];
}
return sharedInstance;
}
Por documentación de Apple:
... Puede adoptar un enfoque similar para sincronizar los métodos de clase de la clase asociada, utilizando el objeto Class en lugar de self.
Incluso si el uso de self funciona, no debería y esto me parece un error de copiar y pegar. La implementación correcta para un método de fábrica de clase sería:
+ (MySingleton*)getInstance
{
@synchronized([MySingleton class])
{
if (sharedInstance == nil)
sharedInstance = [[MySingleton alloc] init];
}
return sharedInstance;
}
Mi camino es tan simple como este:
static id instanceOfXXX = nil;
+ (id) sharedXXX
{
static volatile BOOL initialized = NO;
if (!initialized)
{
@synchronized([XXX class])
{
if (!initialized)
{
instanceOfXXX = [[XXX alloc] init];
initialized = YES;
}
}
}
return instanceOfXXX;
}
Si el singleton ya está inicializado, el bloque LOCK no se ingresará. La segunda verificación si (! Initialized) es para asegurarse de que aún no se haya inicializado cuando el subproceso actual adquiera el LOCK.
No he leído todas las soluciones, así que perdona si este código es redundante.
Esta es la implementación más segura para subprocesos en mi opinión.
+(SingletonObject *) sharedManager
{
static SingletonObject * sharedResourcesObj = nil;
@synchronized(self)
{
if (!sharedResourcesObj)
{
sharedResourcesObj = [[SingletonObject alloc] init];
}
}
return sharedResourcesObj;
}
No quieres sincronizar en ti mismo ... ¡Ya que el objeto self no existe todavía! Terminas bloqueando un valor de id temporal. Desea asegurarse de que nadie más pueda ejecutar métodos de clase (sharedInstance, alloc, allocWithZone :, etc), por lo que necesita sincronizar en el objeto de clase en su lugar:
@implementation MYSingleton
static MYSingleton * sharedInstance = nil;
+( id )sharedInstance {
@synchronized( [ MYSingleton class ] ) {
if( sharedInstance == nil )
sharedInstance = [ [ MYSingleton alloc ] init ];
}
return sharedInstance;
}
+( id )allocWithZone:( NSZone * )zone {
@synchronized( [ MYSingleton class ] ) {
if( sharedInstance == nil )
sharedInstance = [ super allocWithZone:zone ];
}
return sharedInstance;
}
-( id )init {
@synchronized( [ MYSingleton class ] ) {
self = [ super init ];
if( self != nil ) {
// Insert initialization code here
}
return self;
}
}
@end
Normalmente utilizo un código más o menos similar al de la respuesta de Ben Hoffstein (que también saqué de Wikipedia) Lo uso por las razones expuestas por Chris Hanson en su comentario.
Sin embargo, a veces tengo la necesidad de colocar un singleton en un NIB, y en ese caso uso lo siguiente:
@implementation Singleton
static Singleton *singleton = nil;
- (id)init {
static BOOL initialized = NO;
if (!initialized) {
self = [super init];
singleton = self;
initialized = YES;
}
return self;
}
+ (id)allocWithZone:(NSZone*)zone {
@synchronized (self) {
if (!singleton)
singleton = [super allocWithZone:zone];
}
return singleton;
}
+ (Singleton*)sharedSingleton {
if (!singleton)
[[Singleton alloc] init];
return singleton;
}
@end
Dejo la implementación de -retain
(etc.) al lector, aunque el código anterior es todo lo que necesita en un entorno de recolección de basura.
Otra opción es usar el método de +(void)initialize
. De la documentación:
El tiempo de ejecución envía
initialize
a cada clase en un programa exactamente una vez justo antes de que la clase, o cualquier clase que hereda de él, reciba su primer mensaje desde el programa. (Por lo tanto, el método nunca se puede invocar si no se usa la clase). El tiempo de ejecución envía el mensaje deinitialize
a las clases de una manera segura para subprocesos. Las superclases reciben este mensaje antes de sus subclases.
Así que podrías hacer algo parecido a esto:
static MySingleton *sharedSingleton;
+ (void)initialize
{
static BOOL initialized = NO;
if(!initialized)
{
initialized = YES;
sharedSingleton = [[MySingleton alloc] init];
}
}
Para ampliar el ejemplo de @ robbie-hanson ...
static MySingleton* sharedSingleton = nil;
+ (void)initialize {
static BOOL initialized = NO;
if (!initialized) {
initialized = YES;
sharedSingleton = [[self alloc] init];
}
}
- (id)init {
self = [super init];
if (self) {
// Member initialization here.
}
return self;
}
Para una discusión en profundidad del patrón de singleton en Objective-C, mira aquí:
Por mi otra respuesta a continuación, creo que deberías estar haciendo:
+ (id)sharedFoo
{
static dispatch_once_t once;
static MyFoo *sharedFoo;
dispatch_once(&once, ^ { sharedFoo = [[self alloc] init]; });
return sharedFoo;
}
Qué tal si
static MyClass *gInstance = NULL;
+ (MyClass *)instance
{
if (gInstance == NULL) {
@synchronized(self)
{
if (gInstance == NULL)
gInstance = [[self alloc] init];
}
}
return(gInstance);
}
Entonces, ¿evitas el costo de sincronización después de la inicialización?
Respuesta corta: Fabuloso.
Respuesta larga: algo así como ...
static SomeSingleton *instance = NULL;
@implementation SomeSingleton
+ (id) instance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (instance == NULL){
instance = [[super allocWithZone:NULL] init];
}
});
return instance;
}
+ (id) allocWithZone:(NSZone *)paramZone {
return [[self instance] retain];
}
- (id) copyWithZone:(NSZone *)paramZone {
return self;
}
- (id) autorelease {
return self;
}
- (NSUInteger) retainCount {
return NSUIntegerMax;
}
- (id) retain {
return self;
}
@end
Asegúrese de leer el encabezado de dispatch / once.h para comprender lo que está pasando. En este caso, los comentarios de encabezado son más aplicables que los documentos o la página man.
Sé que hay muchos comentarios sobre esta "pregunta", pero no veo que mucha gente sugiera utilizar una macro para definir el singleton. Es un patrón tan común y una macro simplifica enormemente el singleton.
Aquí están las macros que escribí basadas en varias implementaciones de Objc que he visto.
Singeton.h
/**
@abstract Helps define the interface of a singleton.
@param TYPE The type of this singleton.
@param NAME The name of the singleton accessor. Must match the name used in the implementation.
@discussion
Typcially the NAME is something like ''sharedThing'' where ''Thing'' is the prefix-removed type name of the class.
*/
#define SingletonInterface(TYPE, NAME) /
+ (TYPE *)NAME;
/**
@abstract Helps define the implementation of a singleton.
@param TYPE The type of this singleton.
@param NAME The name of the singleton accessor. Must match the name used in the interface.
@discussion
Typcially the NAME is something like ''sharedThing'' where ''Thing'' is the prefix-removed type name of the class.
*/
#define SingletonImplementation(TYPE, NAME) /
static TYPE *__ ## NAME; /
/
/
+ (void)initialize /
{ /
static BOOL initialized = NO; /
if(!initialized) /
{ /
initialized = YES; /
__ ## NAME = [[TYPE alloc] init]; /
} /
} /
/
/
+ (TYPE *)NAME /
{ /
return __ ## NAME; /
}
Ejemplo de uso:
MyManager.h
@interface MyManager
SingletonInterface(MyManager, sharedManager);
// ...
@end
MyManager.m
@implementation MyManager
- (id)init
{
self = [super init];
if (self) {
// Initialization code here.
}
return self;
}
SingletonImplementation(MyManager, sharedManager);
// ...
@end
¿Por qué una macro de interfaz cuando está casi vacía? Coherencia de código entre el encabezado y los archivos de código; mantenibilidad en caso de que desee agregar más métodos automáticos o cambiarlo.
Estoy usando el método de inicialización para crear el singleton como se usa en la respuesta más popular aquí (al momento de escribir).
Solo quería dejar esto aquí para que no lo pierda. La ventaja de este es que se puede usar en InterfaceBuilder, que es una GRAN ventaja. Esto está tomado de otra pregunta que hice :
static Server *instance;
+ (Server *)instance { return instance; }
+ (id)hiddenAlloc
{
return [super alloc];
}
+ (id)alloc
{
return [[self instance] retain];
}
+ (void)initialize
{
static BOOL initialized = NO;
if(!initialized)
{
initialized = YES;
instance = [[Server hiddenAlloc] init];
}
}
- (id) init
{
if (instance)
return self;
self = [super init];
if (self != nil) {
// whatever
}
return self;
}
Tengo una variación interesante en sharedInstance que es seguro para subprocesos, pero no se bloquea después de la inicialización. Todavía no estoy lo suficientemente seguro como para modificar la respuesta principal tal como se solicitó, pero la presento para una discusión adicional:
// Volatile to make sure we are not foiled by CPU caches
static volatile ALBackendRequestManager *sharedInstance;
// There''s no need to call this directly, as method swizzling in sharedInstance
// means this will get called after the singleton is initialized.
+ (MySingleton *)simpleSharedInstance
{
return (MySingleton *)sharedInstance;
}
+ (MySingleton*)sharedInstance
{
@synchronized(self)
{
if (sharedInstance == nil)
{
sharedInstance = [[MySingleton alloc] init];
// Replace expensive thread-safe method
// with the simpler one that just returns the allocated instance.
SEL origSel = @selector(sharedInstance);
SEL newSel = @selector(simpleSharedInstance);
Method origMethod = class_getClassMethod(self, origSel);
Method newMethod = class_getClassMethod(self, newSel);
method_exchangeImplementations(origMethod, newMethod);
}
}
return (MySingleton *)sharedInstance;
}
Una explicación detallada del código de macro de Singleton está en el blog Cocoa With Love
http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html .
Aquí hay una macro que armé:
http://github.com/cjhanson/Objective-C-Optimized-Singleton
Se basa en http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html Pero cambiando la implementación para usar el método swizzling tal como lo describe Dave MacLachlan de Google .
Doy la bienvenida a los comentarios / contribuciones.
Editar: Esta implementación está obsoleta con ARC. Por favor, eche un vistazo a ¿Cómo implemento un singleton de Objective-C que sea compatible con ARC? para su correcta implementación.
Todas las implementaciones de inicialización que he leído en otras respuestas comparten un error común.
+ (void) initialize {
_instance = [[MySingletonClass alloc] init] // <----- Wrong!
}
+ (void) initialize {
if (self == [MySingletonClass class]){ // <----- Correct!
_instance = [[MySingletonClass alloc] init]
}
}
La documentación de Apple recomienda que marque el tipo de clase en su bloque de inicialización. Porque las subclases llaman a la inicialización por defecto. Existe un caso no obvio donde las subclases pueden crearse indirectamente a través de KVO. Por si agrega la siguiente línea en otra clase:
[[MySingletonClass getInstance] addObserver:self forKeyPath:@"foo" options:0 context:nil]
Objective-C creará implícitamente una subclase de MySingletonClass que resultará en una segunda activación de +initialize
.
Puede pensar que debería verificar implícitamente la inicialización duplicada en su bloque de inicio como tal:
- (id) init { <----- Wrong!
if (_instance != nil) {
// Some hack
}
else {
// Do stuff
}
return self;
}
Pero te dispararás en el pie; O peor, dale a otro desarrollador la oportunidad de dispararse en el pie.
- (id) init { <----- Correct!
NSAssert(_instance == nil, @"Duplication initialization of singleton");
self = [super init];
if (self){
// Do stuff
}
return self;
}
TL; DR, aquí está mi implementación
@implementation MySingletonClass
static MySingletonClass * _instance;
+ (void) initialize {
if (self == [MySingletonClass class]){
_instance = [[MySingletonClass alloc] init];
}
}
- (id) init {
ZAssert (_instance == nil, @"Duplication initialization of singleton");
self = [super init];
if (self) {
// Initialization
}
return self;
}
+ (id) getInstance {
return _instance;
}
@end
(Reemplace ZAssert con nuestra propia macro de aserción; o simplemente NSAssert).
static MyClass *sharedInst = nil; + (id)sharedInstance { @synchronize( self ) { if ( sharedInst == nil ) { /* sharedInst set up in init */ [[self alloc] init]; } } return sharedInst; } - (id)init { if ( sharedInst != nil ) { [NSException raise:NSInternalInconsistencyException format:@"[%@ %@] cannot be called; use +[%@ %@] instead"], NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSStringFromClass([self class]), NSStringFromSelector(@selector(sharedInstance)"]; } else if ( self = [super init] ) { sharedInst = self; /* Whatever class specific here */ } return sharedInst; } /* These probably do nothing in a GC app. Keeps singleton as an actual singleton in a non CG app */ - (NSUInteger)retainCount { return NSUIntegerMax; } - (oneway void)release { } - (id)retain { return sharedInst; } - (id)autorelease { return sharedInst; }
@interface MySingleton : NSObject
{
}
+ (MySingleton *)sharedSingleton;
@end
@implementation MySingleton
+ (MySingleton *)sharedSingleton
{
static MySingleton *sharedSingleton;
@synchronized(self)
{
if (!sharedSingleton)
sharedSingleton = [[MySingleton alloc] init];
return sharedSingleton;
}
}
@end
static mySingleton *obj=nil;
@implementation mySingleton
-(id) init {
if(obj != nil){
[self release];
return obj;
} else if(self = [super init]) {
obj = self;
}
return obj;
}
+(mySingleton*) getSharedInstance {
@synchronized(self){
if(obj == nil) {
obj = [[mySingleton alloc] init];
}
}
return obj;
}
- (id)retain {
return self;
}
- (id)copy {
return self;
}
- (unsigned)retainCount {
return UINT_MAX; // denotes an object that cannot be released
}
- (void)release {
if(obj != self){
[super release];
}
//do nothing
}
- (id)autorelease {
return self;
}
-(void) dealloc {
[super dealloc];
}
@end