2010-02-19 6 views
6

Esto es lo que he inventado, después de revisar la literatura de singleton. ¿He olvidado algo?¿Es esta la implementación de Singleton de Objective C refinada definitivamente?

@implementation MySingleton 

static MySingleton *mySharedInstance = nil; 

//called by atexit on exit, to ensure all resources are freed properly (not just memory) 
static void singleton_remover() 
{ 
    //free resources here 
} 

+ (MySingleton*) sharedInstance{ 
    return mySharedInstance; 
} 

+ (void)initialize { 
    if (self == [MySingleton class]) { 
     mySharedInstance = [[super allocWithZone:NULL] init]; 
    atexit(singleton_remover);  
    } 
} 

+ (id)allocWithZone:(NSZone *)zone 
{ 
    return [self sharedInstance]; 
} 

- (id)copyWithZone:(NSZone *)zone 
{ 
    return self;  
} 

- (id)retain 
{ 
    return self;  
} 

- (NSUInteger)retainCount 
{ 
    return NSUIntegerMax; //denotes an object that cannot be released 
} 

- (void)release 
{ 
    //do nothing  
} 

- (id)autorelease 
{ 
    return self;  
} 
+1

No edite su pregunta de forma que invalide o deje de ser relevante. – dreamlax

+0

¿Por qué otro subproceso singleton de Objective-C? Ver http://stackoverflow.com/questions/145154 para ejemplos. – zoul

+0

¿No usaría Gallagher's .. http://projectswithlove.com/projects/SynthesizeSingleton.h.zip desde http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html. ..? – Fattie

Respuesta

2

que evita el bloqueo de sincronización más del tiempo

si desea que su software sea fiable, evitar construcciones que funcionan "la mayor parte del tiempo"

http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html

Tabla 4.1. Cerradura con doble verificación

Una cerradura con doble verificación es un intento de reducir la sobrecarga de tomar una cerradura probando los criterios de bloqueo antes de tomar la cerradura. Debido a que los bloqueos doblemente chequeados son potencialmente inseguros, el sistema no proporciona soporte explícito para ellos y se desaconseja su uso.

+0

-1 para irrelevancia y flamebait. –

+0

Creo que estaba pensando "ahorra CPU y sincronización la mayor parte del tiempo". Sin embargo, con las CPU y los optimizadores modernos, es muy posible que "haga lo correcto la mayor parte del tiempo". Pero admito que la observación de Windows no fue necesaria. – Darron

+0

Pero el bloqueo comprobado _es_ no es confiable, ¿o estoy equivocado? * editar: * Tengo razón. http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html – glebm

1

Algunas sugerencias (más para Mac cacao que el iPhone, pero puede ser útil para otras personas que buscan la etiqueta de Objective-C):

  • No se moleste con -allocWithZone: nulo, solo plain -alloc va a estar bien.
  • Considere el uso de dispatch_once() o pthread_once() cuando se disponga
  • El uso atexit es inteligente, pero puede no ser compatible con la terminación rápida aplicación (no estoy seguro si esto se aplica en el iPhone), ya que matar con eficacia la aplicación -9s

Un modelo de la diversión adicional:

+ (Foo *)sharedFoo { 
    static Foo *sharedInstance = NULL; 
    if (!sharedInstance) { 
     Foo *temp = [[Foo alloc] init]; //NOTE: This MUST NOT have side effects for it to be threadsafe 
     if (!OSAtomicCompareAndSwapPtrBarrier(NULL, temp, &sharedInstance)) { 
      [temp release]; 
     } 
    } 
    return sharedInstance; 
} 
+0

- Usar alloc llamará a mi overcup allocWithZone, que tendrá nil en sharedInstance, por lo que este sería un problema – Jacko

+0

Ah, por supuesto. Ignore ese bit entonces :) –

0

singleton_remover La función no va a hacer nada, porque se ha redefinido release no hacer nada. El método allocWithZone: también realiza una operación no operativa similar cuando envía retain a la instancia compartida (y omite por completo la asignación en la zona especificada). Tal vez deberías tener una bandera que alterne si tu instancia compartida es invencible (es decir, no liberable) o no.

De cualquier manera, el sistema operativo limpiará toda la memoria de todos modos. La documentación indica que es más rápido para el sistema operativo recuperar simplemente toda la memoria de una vez que para que su aplicación la devuelva lentamente pieza por pieza.

Si la instancia compartida es la gestión de los recursos que siempre necesitan ser limpiados cuando la aplicación termina, usted debe tener que registrarse para recibir el , y llevar a cabo la limpieza de allí.

[[NSNotificationCenter defaultCenter] addObserver:self 
             selector:@selector(performCleanup:) 
              name:UIApplicationWillTerminateNotification 
              object:nil]; 
+0

gracias. ¿Qué hay de malo en liberar recursos en el método singleton_remover? – Jacko

+0

Respondiendo a la 'NS/UIApplicationWillTerminateNotification' es la forma documentada de manejar con elegancia la administración de recursos durante la finalización de la aplicación. – dreamlax

+0

Solo es seguro registrarse para 'NS/UIApplicationWillTerminateNotification' en el caso general solo en el hilo principal; asegúrese de tomarlo en consideración – rpetrich

0

EDITAR

Estoy incluyendo esta en la parte superior, a continuación se puede ver mis preguntas originales históricos y aplicación.Sin embargo creo que he encontrado la mejor manera de proporcionar un método sharedInstance con ninguna sobrecarga de bloqueo, me gustaría escuchar las posibles preocupaciones acerca de esto:

// 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 orig = @selector(sharedInstance); 
      SEL new = @selector(simpleSharedInstance); 
      Method origMethod = class_getClassMethod(self, orig); 
      Method newMethod = class_getClassMethod(self, new); 
      method_exchangeImplementations(origMethod, newMethod); 
     } 
    } 
    return (MySingleton *)sharedInstance; 
} 

Y la discusión histórica en torno a inicializar:

I ver ahora el código original era bastante parecido al mío (abajo), excepto por tener el cheque de la instancia fuera del candado.

Aunque el nuevo método de inicialización + (vacío) es interesante, no estoy seguro de que me guste más. Parece que ahora para obtener una instancia singleton, ahora siempre debe llamar:

MySingleton instance = [[MySingleton alloc] init]; 

¿No es eso correcto? Eso se siente extraño, ¿y es más eficiente si la llamada para inicializar ya está bloqueada para usted? El método de doble bloqueo parece funcionar bien para este caso de uso, al tiempo que también evita el bloqueo (al costo potencial de una doble asignación, creo que debido a que más de un hilo podría caer a través del if).

La otra cosa que parece extraña si, si el método de inicialización fuera realmente preferido, ¿por qué no lo vemos en otro lugar? Objective-C ha existido por MUCHO tiempo y desconfío de los mecanismos fundamentales que difieren de casi todos los ejemplos publicados.

Mi código Actualmente uso (que refleja que he visto en otros lugares, incluyendo this respuesta):

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

+ (id)allocWithZone:(NSZone *)zone 
{ 
    @synchronized(self) 
    { 
     if (sharedInstance == nil) 
     { 
      sharedInstance = [super allocWithZone:zone]; 
      return sharedInstance; // assignment and return on first allocation 
     } 
    } 
    return nil; // on subsequent allocation attempts return nil 
} 
+0

Interesante. Mi implementación evita bloqueos por completo, para un mejor rendimiento y sin potencial de interbloqueo. Además, debe sincronizar en [clase MySingleton], ya que el auto no se ha inicializado aún. – Jacko

+0

@Jacko, eso no es cierto, 'self' se inicializará (es decir, con' + initialize') antes de que se le envíen mensajes, ya sea que invoquen métodos de clase o instancia. – dreamlax

+0

En un método de nivel de clase, "self" es equivalente a [MyClass class] - Es decir, BOOL isSelf = self == [MySingleton class]; se establecerá en SÍ en el método sharedInstance anterior. Además, como el código está escrito, no hay posibilidad de bloqueos debido a la secuencia de llamadas ... sin embargo, estoy de acuerdo en que el método de "inicialización" es probablemente mejor. ¿Cómo se llega a la variable de instancia compartida desde fuera de la clase? No vi ese método. –

0

Su aplicación es seguro para subprocesos y parece cubrir todas las bases (+ inicializar se envía thread- seguro por el tiempo de ejecución)

editar: No es seguro llamar a muchos códigos durante una función atexit. El registro para en el hilo principal es más seguro.

edición2: He destilado y refinado el patrón que uso en una macro. -init se llama una vez que se llama a +sharedInstance y se llamará -dealloc cuando finaliza la aplicación.

#define IMPLEMENT_UIAPP_SINGLETON(class_name) \ 
static class_name *shared ## class_name; \ 
+ (void)cleanupFromTerminate \ 
{ \ 
    class_name *temp = shared ## class_name; \ 
    shared ## class_name = nil; \ 
    [temp dealloc]; \ 
} \ 
+ (void)registerForCleanup \ 
{ \ 
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanupFromTerminate) name:UIApplicationWillTerminateNotification object:nil]; \ 
} \ 
+ (void)initialize { \ 
    if (self == [class_name class]) { \ 
     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; \ 
     if ([NSThread isMainThread]) \ 
      [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanupFromTerminate) name:UIApplicationWillTerminateNotification object:nil]; \ 
     else \ 
      [self performSelectorOnMainThread:@selector(registerForCleanup) withObject:nil waitUntilDone:NO]; \ 
     shared ## class_name = [[super allocWithZone:NULL] init]; \ 
     [pool drain]; \ 
    } \ 
} \ 
+ (class_name *)sharedInstance \ 
{ \ 
    return shared ## class_name; \ 
} \ 
+ (id)allocWithZone:(NSZone *)zone \ 
{ \ 
    return shared ## class_name; \ 
} \ 
- (id)copyWithZone:(NSZone *)zone \ 
{ \ 
    return self; \ 
} \ 
- (id)retain \ 
{ \ 
    return self; \ 
} \ 
- (NSUInteger)retainCount \ 
{ \ 
    return NSUIntegerMax; \ 
} \ 
- (void)release \ 
{ \ 
} \ 
- (id)autorelease \ 
{ \ 
    return self; \ 
} 
+0

Apple dijo que no use métodos con nombres que comiencen con guiones bajos. El prefijo de subrayado se reserva y se utiliza para los métodos privados de Apple. – dreamlax

+0

Muy buen punto (según http://www.devworld.apple.com/iphone/library/documentation/Cocoa/Conceptual/ObjectiveC/Articles/ocLanguageSummary.html#//apple_ref/doc/uid/TP30001163-CH3-TPXREF108); editado para reflejar – rpetrich

+0

¿por qué necesita un grupo de autorrelease en el método de inicialización? – Jacko

Cuestiones relacionadas