¿Cómo debería ser mi singleton Objective-C? [cerrado]


334

Mi método de acceso único 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?


27
Lo que tiene está bien, aunque podría mover la declaración de variable global a su método de instancia + (el único lugar donde debe usarse, a menos que también permita que se establezca) y usar un nombre como + defaultMyClass o + sharedMyClass para su método. + instancia no revela la intención.
Chris Hanson

Dado que es poco probable que la 'respuesta' a esta pregunta cambie pronto, estoy colocando un bloqueo histórico en la pregunta. Dos razones 1) Muchos puntos de vista, votos y buen contenido 2) Para evitar el yoyo de abierto / cerrado. Fue una gran pregunta para su tiempo, pero las preguntas de este tipo no son apropiadas para Stack Overflow. Ahora tenemos Revisión de código para verificar el código de trabajo. Lleve toda la discusión de esta pregunta a esta meta pregunta .
George Stocker

Respuestas:


207

Otra opción es usar el +(void)initializemétodo. De la documentación:

El tiempo de ejecución se envía initializea cada clase en un programa exactamente una vez justo antes de que la clase, o cualquier clase que herede de ella, envíe 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 initializemensaje a las clases de manera segura para subprocesos. Las superclases reciben este mensaje antes que sus subclases.

Entonces podrías hacer algo similar a esto:

static MySingleton *sharedSingleton;

+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        sharedSingleton = [[MySingleton alloc] init];
    }
}

77
Si el tiempo de ejecución solo llamará a esto una vez, ¿qué hace el BOOL? ¿Es eso una precaución en caso de que alguien llame a esta función explícitamente desde su código?
Aftermathew

55
Sí, es una precaución ya que la función también se puede llamar directamente.
Robbie Hanson

33
Esto también es necesario porque podría haber subclases. Si no anulan, +initializesu implementación de superclases se llamará si la subclase se usa por primera vez.
Sven

3
@Paul puedes anular el releasemétodo y dejarlo vacío. :)

44
@aryaxt: de los documentos enumerados, esto ya es seguro para subprocesos. Entonces, la llamada es una vez por tiempo de ejecución - período. Esta parece ser la solución correcta, segura para subprocesos y óptimamente eficiente.
lilbyrdie

95
@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]


77
Esto es todo lo que normalmente debes usar para los singletons. Entre otras cosas, mantener tus clases por separado instanciables hace que sean más fáciles de probar, porque puedes probar instancias separadas en lugar de tener una forma de restablecer su estado.
Chris Hanson

3
Stig Brautaset: No, no está bien omitir el @synchronized en este ejemplo. Está allí para manejar la posible condición de carrera de dos subprocesos que ejecutan esta función estática al mismo tiempo, ambos superando la prueba "if (! SharedSingleton)" al mismo tiempo, y dando como resultado dos [MySingleton alloc] s. .. El {synchronized {scope block} obliga a ese hipotético segundo thread a esperar que el primer thread salga del {scope block} antes de que se le permita continuar. ¡Espero que esto ayude! =)
MechEthan

3
¿Qué impide que alguien siga haciendo su propia instancia del objeto? MySingleton *s = [[MySingelton alloc] init];
lindon fox

1
@lindonfox ¿Cuál es la respuesta a tu pregunta?
Raffi Khatchadourian

1
@ Raffi: lo siento, creo que debí haber olvidado pegar mi respuesta. De todos modos, obtuve el libro Pro Objective-C Design Patterns for iOSy explica cómo se hace un singelton "estricto". Básicamente, dado que no puede hacer que los métodos de inicio sean privados, debe anular los métodos alloc y copy. Entonces, si intenta hacer algo como [[MySingelton alloc] init]esto, obtendrá un error de tiempo de ejecución (aunque desafortunadamente no es un error de tiempo de compilación). No entiendo cómo todos los detalles de la creación de objetos, pero se implementa + (id) allocWithZone:(NSZone *)zonelo que se llama ensharedSingleton
Lindon zorro

59

Según 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;
}

66
No te molestes con todo lo que estás haciendo arriba. Haga que sus singletons (con suerte extremadamente pocos) sean instanciables por separado, y solo tenga un método compartido / predeterminado. Lo que has hecho solo es necesario si realmente solo quieres una sola instancia de tu clase. Lo que no sabes, especialmente. para pruebas unitarias.
Chris Hanson

La cuestión es que este es el código de muestra de Apple para "crear un singleton". Pero sí, tienes toda la razón.
Colin Barrett

1
El código de muestra de Apple es correcto si desea un singleton "verdadero" (es decir, un objeto que solo se puede instanciar una vez), pero como dice Chris, esto rara vez es lo que quiere o necesita, mientras que algún tipo de instancia compartida configurable es lo que usted desea. generalmente quiero.
Luke Redpath el

Aquí hay una macro para el método anterior: gist.github.com/1057420 . Esto es lo que yo uso.
Kobski

1
Dejando a un lado las pruebas unitarias, no hay nada en contra de esta solución, ¿correcto? Y es rápido y seguro.
LearnCocos2D

58

Dado que Kendall publicó un singleton seguro para hilos que intenta evitar los costos de bloqueo, pensé en tirar uno también:

#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 sharedInstanceya se ha establecido, por lo que el whilebucle nunca se ejecuta y la función regresa después de simplemente probar la existencia de la variable;

  2. Caso lento: si sharedInstanceno existe, se asigna una instancia y se copia en ella utilizando un Comparar y cambiar ('CAS');

  3. Caso contenido: si dos subprocesos intentan llamar sharedInstanceal mismo tiempo Y sharedInstance no existe al mismo tiempo, ambos inicializarán nuevas instancias del singleton e intentarán ponerlo en CAS. El que gane el CAS regresa inmediatamente, el que pierde libera la instancia que acaba de asignar y devuelve el (ahora configurado) sharedInstance. El single OSAtomicCompareAndSwapPtrBarrieractúa como una barrera de escritura para el hilo de configuración y una barrera de lectura del hilo de prueba.


18
Esto es una exageración completa por lo menos una vez que puede suceder durante la vida útil de una aplicación. Sin embargo, es correcto y la técnica de comparar e intercambiar es una herramienta útil para conocer, entonces +1.
Steve Madsen

Buena respuesta: la familia OSAtomic es algo bueno que se debe saber
Bill

1
@Louis: ¡Una respuesta increíble, realmente esclarecedora! Sin embargo, una pregunta: ¿qué debería hacer mi initmétodo en su enfoque? Lanzar una excepción cuando sharedInstancese inicializa no es una buena idea, creo. ¿Qué hacer para evitar que el usuario llame initdirectamente muchas veces?
matm

2
Generalmente no lo evito. A menudo hay razones válidas para permitir que lo que generalmente es un singleton se multiplique, el más común es para ciertos tipos de pruebas unitarias. Si realmente quisiera imponer una instancia única, probablemente haría que el método init verificara si el global existía, y si lo tuviera, lo liberaría y devolvería el global.
Louis Gerbarg

1
@Tony tardó un poco en responder, pero OSAtomicCompareAndSwapPtrBarrier requiere un volátil. ¿Quizás la palabra clave volátil es evitar que el compilador optimice la verificación? Ver: stackoverflow.com/a/5334727/449161 y developer.apple.com/library/mac/#documentation/Darwin/Reference/…
Ben Flynn

14
MyClass estático * sharedInst = nil;

+ (id) sharedInstance
{
    @synchronize (self) {
        if (sharedInst == nil) {
            / * sharedInst configurado en init * /
            [[autoasignación] init];
        }
    }
    return sharedInst;
}

- (id) init
{
    if (sharedInst! = nil) {
        [Aumento de NSException: NSInternalInconsistencyException
            formato: @ "[% @% @] no se puede llamar; use + [% @% @] en su lugar"],
            NSStringFromClass ([clase propia]), NSStringFromSelector (_cmd), 
            NSStringFromClass ([clase propia]),
            NSStringFromSelector (@selector (sharedInstance) "];
    } else if (self = [super init]) {
        sharedInst = self;
        / * Cualquiera que sea la clase específica aquí * /
    }
    return sharedInst;
}

/ * Estos probablemente no hacen nada en
   una aplicación GC. Mantiene singleton
   como un singleton real en un
   aplicación no CG
* /
- (NSUInteger) retieneCount
{
    return NSUIntegerMax;
}

- liberación (unidireccional)
{
}

- (id) retener
{
    return sharedInst;
}

- (id) lanzamiento automático
{
    return sharedInst;
}

3
Noté que clang se queja de una fuga si no asigna el resultado de [[self alloc] init]sharedInst.
pix0r

Subvertir init como este es un enfoque bastante feo de la OMI. No te metas con init y / o la creación real del objeto. Si, en cambio, busca un punto de acceso controlado a una instancia compartida, mientras que no está insertando el singleton en el objeto, tendrá un momento más feliz si escribe pruebas, etc. Los singletons duros son demasiado utilizados.
oculto

12

Editar: Esta implementación está obsoleta con ARC. Por favor, eche un vistazo a ¿Cómo implemento un singleton Objective-C que sea compatible con ARC? Para una correcta implementación.

Todas las implementaciones de initialize 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 verifique el tipo de clase en su bloque de inicialización. Porque las subclases llaman a initialize por defecto. Existe un caso no obvio en el que se pueden crear subclases indirectamente a través de KVO. Porque 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 dando como resultado una segunda activación de +initialize.

Puede pensar que debe 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, dar a otro desarrollador la oportunidad de pegarse un tiro 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).


1
Simplemente viviría más simple y evitaría la inicialización por completo.
Tom Andersen


9

Tengo una variación interesante en sharedInstance que es segura 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 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;
}

1
+1 eso es realmente intrigante. Podría usar class_replaceMethodpara transformarme sharedInstanceen un clon de simpleSharedInstance. De esa forma, nunca más tendrá que preocuparse por adquirir un @synchronizedcandado nuevamente.
Dave DeLong

Es el mismo efecto, usar exchangeImplementations significa que después de init cuando llamas a sharedInstance, realmente estás llamando a simpleSharedInstance. De hecho, comencé con replaceMethod, pero decidí que era mejor cambiar las implementaciones para que el original aún existiera si fuera necesario ...
Kendall Helmstetter Gelner

En otras pruebas, no pude hacer que replaceMethod funcione: en llamadas repetidas, el código todavía llamaba original SharedInstance en lugar de simpleSharedInstance. Creo que puede ser porque ambos son métodos de nivel de clase ... El reemplazo que usé fue: class_replaceMethod (self, origSel, method_getImplementation (newMethod), method_getTypeEncoding (newMethod)); y algunas variaciones de los mismos. Puedo verificar el código que he publicado y se llama a simpleSharedInstance después del primer paso a través de sharedInstance.
Kendall Helmstetter Gelner

Puede hacer una versión segura para subprocesos que no pague los costos de bloqueo después de la inicialización sin hacer un montón de mucking de tiempo de ejecución, he publicado una implementación a continuación.
Louis Gerbarg

1
+1 gran idea. Me encantan las cosas que uno puede hacer con el tiempo de ejecución. Pero en la mayoría de los casos esto probablemente sea una optimización prematura. Si realmente tuviera que deshacerme del costo de sincronización, probablemente usaría la versión sin bloqueo de Louis.
Sven

6

Respuesta corta: fabulosa.

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 dispatch / once.h para comprender lo que está sucediendo. En este caso, los comentarios del encabezado son más aplicables que los documentos o la página de manual.


5

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

#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 de NSObject. Pero la mayoría de las veces uso singletons en mi código, de hecho, son subclases NSObject, por lo que esta clase realmente me facilita la vida y hace que el código sea más limpio.


Es posible que desee utilizar algún otro mecanismo de bloqueo porque @synchronizedes terriblemente lento y debe evitarse.
DarkDust

2

Esto funciona en un entorno no recolectado de basura también.

@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

2

¿No debería ser seguro 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;
}

2
La técnica de bloqueo de doble verificación utilizada aquí a menudo es un problema real en algunos entornos (consulte aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf o Google). Hasta que se muestre lo contrario, asumiría que Objective-C no es inmune. También vea wincent.com/a/knowledge-base/archives/2006/01/… .
Steve Madsen


2

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?


Vea las discusiones sobre el bloqueo de doble verificación en otras respuestas.
i_am_jorf


1

KLSingleton es:

  1. Subclasible (hasta el enésimo grado)
  2. ARC compatible
  3. Caja fuerte con allocyinit
  4. Cargado perezosamente
  5. A salvo de amenazas
  6. Sin bloqueo (usa + initialize, no @synchronize)
  7. Libre de macros
  8. Sin swizzle
  9. Sencillo

KLSingleton


1
Estoy usando su NSSingleton para mi proyecto, y parece ser incompatible con KVO. El asunto es que KVO crea una subclase para cada objeto KVO con el prefijo NSKVONotifying_ MyClass . Y hace que MyClass + se inicialice y se inicien los métodos dos veces.
Oleg Trakhman

Probé esto en el último Xcode y no tuve ningún problema para registrarme o recibir eventos de KVO. Puede verificar esto con el siguiente código: gist.github.com/3065038 Como mencioné en Twitter, los métodos + initialize se llaman una vez para NSSingleton y una vez para cada subclase. Esta es una propiedad de Objective-C.
kevinlawler

Si agrega NSLog(@"initialize: %@", NSStringFromClass([self class]));al +initializemétodo, puede verificar que las clases se inicialicen solo una vez.
kevinlawler

NSLog (@ "initialize:% @", NSStringFromClass ([clase propia]));
Oleg Trakhman

Es posible que también desee que sea compatible con IB. El mío es: stackoverflow.com/questions/4609609/…
Dan Rosenstark

0

No desea sincronizar con uno mismo ... ¡Ya que el objeto propio todavía no existe! Terminas bloqueando un valor de identificación temporal. Desea asegurarse de que nadie más pueda ejecutar métodos de clase (sharedInstance, alloc, allocWithZone :, etc.), por lo que debe sincronizar 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

1
El resto de los métodos, métodos de acceso, métodos de mutadores, etc. deben sincronizarse en uno mismo. Todos los métodos e inicializadores de clase (+) (y probablemente -dealloc) deberían sincronizarse en el objeto de clase. Puede evitar tener que sincronizar manualmente si usa las propiedades de Objective-C 2.0 en lugar de los métodos de acceso / mutación. Todos object.property y object.property = foo, se sincronizan automáticamente con self.
Rob Dotson el

3
Explica por qué crees que el selfobjeto no existe en un método de clase. El tiempo de ejecución determina qué implementación de método invocar en función del mismo valor exacto que proporciona selfpara cada método (clase o instancia).
dreamlax

2
Dentro de un método de clase, self está el objeto de clase. Inténtelo usted mismo:#import <Foundation/Foundation.h> @interface Eggbert : NSObject + (BOOL) selfIsClassObject; @end @implementation Eggbert + (BOOL) selfIsClassObject { return self == [Eggbert class]; } @end int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSLog(@"%@", [Eggbert selfIsClassObject] ? @"YES" : @"NO"); [pool drain]; return 0; }
CSJs

0

Solo quería dejar esto aquí para no perderlo. La ventaja de este es que se puede usar en InterfaceBuilder, que es una GRAN ventaja. Esto se toma 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;
}

0
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

0

Sé que hay muchos comentarios sobre esta "pregunta", pero no veo que muchas personas sugieran usar 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? Consistencia del 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 initialize para crear el singleton como se usa en la respuesta más popular aquí (al momento de escribir).


0

Con los métodos de clase Objective C, podemos evitar usar el 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 instancias duplicadas accidentalmente, ¡ya que no estamos creando ninguna instancia!

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


Su enlace ya no funciona.
i_am_jorf

0

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

0

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, no se ingresará el bloque LOCK. La segunda comprobación si (! Initialized) es para asegurarse de que aún no se haya inicializado cuando el hilo actual adquiera el LOCK.


No está claro que marcar initializedcomo volatilees suficiente. Ver aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf .
i_am_jorf

0

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

-4

Usualmente 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 los motivos expuestos por Chris Hanson en su comentario.

Sin embargo, a veces tengo la necesidad de colocar un singleton en una 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.


2
Su código no es seguro para subprocesos. Utiliza sincronizado en el método alloc, pero no en el método init. Verificar el bool inicializado no es seguro para subprocesos.
Mecki

-5

La respuesta aceptada, aunque se 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 usa trabajos por cuenta propia, no debería y esto me parece un error de copiar y pegar. La implementación correcta para un método de clase de fábrica sería:

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

66
auto sin duda lo hace existir es ámbito de clase. Se refiere a la clase en lugar de la instancia de la clase. Las clases son (principalmente) objetos de primera clase.
schwa

¿Por qué pones @synchroninzed DENTRO de un método?
user4951

1
Como ya dijo schwa, self es el objeto de clase dentro de un método de clase. Vea mi comentario para un fragmento que demuestre esto.
jscs

selfexiste, pero usarlo como el identificador pasado @synchronizedsincronizará el acceso a los métodos de la instancia. Como señala @ user490696, hay casos (como singletons) en los que es preferible usar el objeto de clase. De la Guía de programación Obj-C:You can take a similar approach to synchronize the class methods of the associated class, using the class object instead of self. In the latter case, of course, only one thread at a time is allowed to execute a class method because there is only one class object that is shared by all callers.
quellish
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.