Pregunta

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?

¿Fue útil?

Solución

Otra opción es usar el método + (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 ella, reciba su primer mensaje desde dentro del 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 initialize 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];
    }
}

Otros consejos

@interface MySingleton : NSObject
{
}

+ (MySingleton *)sharedSingleton;
@end

@implementation MySingleton

+ (MySingleton *)sharedSingleton
{
  static MySingleton *sharedSingleton;

  @synchronized(self)
  {
    if (!sharedSingleton)
      sharedSingleton = [[MySingleton alloc] init];

    return sharedSingleton;
  }
}

@end

[Fuente]

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;
}

Desde Kendall publicó un mensaje seguro Singleton 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:

  1. Caso rápido: en la ejecución normal, sharedInstance ya se ha establecido, por lo que el while nunca se ejecuta y la función regresa después de simplemente probar la existencia de la variable ;

  2. Caso lento: si sharedInstance no existe, entonces se asigna una instancia y se copia en ella utilizando un Compare And Swap ('CAS');

  3. Caso contendido: si dos subprocesos intentan llamar sharedInstance al mismo tiempo, AND sharedInstance no existe en el 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 el (código ahora compartido) sharedInstance . El único OSAtomicCompareAndSwapPtrBarrier actúa como una barrera de escritura para el hilo de configuración y una barrera de lectura del hilo de prueba.

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;
}

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 la implementación correcta.

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.)

Una explicación detallada del código de macro de Singleton se encuentra en el blog Cocoa With Love

http://cocoawithlove.com/2008/11 /singletons-appdelegates-and-top-level.html .

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 según lo solicitado, pero lo presento para una discusión más profunda:

// 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;
}

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 entender que esta pasando. En este caso, los comentarios del encabezado son más aplicables que los documentos o la página del manual.

He incluido singleton en una clase, por lo que otras clases pueden heredar propiedades de 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.

Esto también funciona en un entorno no recolectado como 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

¿No debería ser seguro para los subprocesos y evitar el costoso cuidado de la primera llamada?

+ (MySingleton*)sharedInstance
{
    if (sharedInstance == nil) {
        @synchronized(self) {
            if (sharedInstance == nil) {
                sharedInstance = [[MySingleton alloc] init];
            }
        }
    }
    return (MySingleton *)sharedInstance;
}

Aquí hay una macro que reuní:

http://github.com/cjhanson/Objective-C-Optimized-Singleton

Se basa en el trabajo aquí por Matt Gallagher Pero cambiando la implementación para usar el método como lo describe Dave MacLachlan de Google .

Doy la bienvenida a los comentarios / contribuciones.

¿Qué tal

static MyClass *gInstance = NULL;

+ (MyClass *)instance
{
    if (gInstance == NULL) {
        @synchronized(self)
        {
            if (gInstance == NULL)
                gInstance = [[self alloc] init];
        }
    }

    return(gInstance);
}

¿Entonces evita el costo de sincronización después de la inicialización?

Para una discusión en profundidad del patrón de singleton en Objective-C, mira aquí:

Uso del patrón Singleton en Objective-C

  

KLSingleton es:

     
      
  1. Subclasible (en el grado n-th)
  2.   
  3. compatible con ARC
  4.   
  5. Seguro con alloc y init
  6.   
  7. cargado perezosamente
  8.   
  9. Hilo seguro
  10.   
  11. Sin bloqueo (usa + inicializa, no @synchronize)
  12.   
  13. Macro-free
  14.   
  15. Swizzle-free
  16.   
  17. Simple
  18.   

KLSingleton

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 debe sincronizar en el objeto de la 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

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;
}
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

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).

Con los métodos de clase de Objective C, podemos evitar el uso del patrón singleton de la manera habitual, desde:

[[Librarian sharedInstance] openLibrary]

a:

[Librarian openLibrary]

envolviendo la clase dentro de otra clase que solo tiene Métodos de clase , de esa manera no hay posibilidad de crear accidentalmente instancias duplicadas, ¡ya que no estamos creando ninguna instancia!

Escribí un blog más detallado aquí :)

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;
}

Mi camino es 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;
}

Por lo general uso 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.

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:

... Puedes 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;
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top