2011-09-29 18 views
27

Encontré información en la red para crear una clase singleton usando GCD. Eso es genial porque es seguro para subprocesos con muy poca sobrecarga. Lamentablemente no pude encontrar soluciones completas, sino solo fragmentos del método sharedInstance. Así que hice mi propia clase utilizando el método de ensayo y error - y et voila - la siguiente salió:¿Corregir el patrón Singleton Objective C (iOS)?

@implementation MySingleton 

// MARK: - 
// MARK: Singleton Pattern using GCD 

+ (id)allocWithZone:(NSZone *)zone { return [[self sharedInstance] retain]; } 
- (id)copyWithZone:(NSZone *)zone { return self; } 
- (id)autorelease { return self; } 
- (oneway void)release { /* Singletons can't be released */ } 
- (void)dealloc { [super dealloc]; /* should never be called */ } 
- (id)retain { return self; } 
- (NSUInteger)retainCount { return NSUIntegerMax; /* That's soooo non-zero */ } 

+ (MySingleton *)sharedInstance 
{ 
    static MySingleton * instance = nil; 

    static dispatch_once_t predicate; 
    dispatch_once(&predicate, ^{ 
     // --- call to super avoids a deadlock with the above allocWithZone 
     instance = [[super allocWithZone:nil] init]; 
    }); 

    return instance; 
} 

// MARK: - 
// MARK: Initialization 

- (id)init 
{ 
    self = [super init]; 
    if (self) 
    { 
     // Initialization code here. 
    } 
    return self; 
} 

@end 

No dude en comentar y dime si he perdiendo algo o hacer algo completamente equivocado;)

Saludos Stefan

+0

estaría tentado a agregue un '- (void) dealloc' que arroje una excepción, de esa manera usted debería ser capaz de rastrear al actor ofensor si alguien está obteniendo una instancia de singleton y luego liberándola. Además de ser un abuso del patrón, eso te dejará con un puntero colgando. – Tommy

+0

meta pregunta: ¿debería ser esto en [http://codereview.stackexchange.com/]? – Joren

+2

apple recomienda encarecidamente no crear singletons anulando retención/liberación! esto interrumpirá las aplicaciones que cambian a ARC –

Respuesta

81

Manténgalo simple:

+(instancetype)sharedInstance 
{ 
    static dispatch_once_t pred; 
    static id sharedInstance = nil; 
    dispatch_once(&pred, ^{ 
     sharedInstance = [[self alloc] init]; 
    }); 
    return sharedInstance; 
} 

- (void)dealloc 
{ 
    // implement -dealloc & remove abort() when refactoring for 
    // non-singleton use. 
    abort(); 
} 

Eso es todo. Anulando retain, release, retainCount y el resto solo está ocultando errores y agregando un montón de líneas de código innecesario. Cada línea de código es un error esperando a suceder. En realidad, si está causando que se llame a dealloc en su instancia compartida, tiene un error muy grave en su aplicación. Ese error debería arreglarse, no ocultarse.

Este enfoque también se presta a la refactorización para admitir modos de uso no únicos.Casi todos los singleton que sobreviven más allá de algunos lanzamientos serán eventualmente refactorizados en una forma no única. Algunos (como NSFileManager) continúan admitiendo un modo singleton al tiempo que admiten la creación de instancias arbitrarias.

Tenga en cuenta que lo anterior también "simplemente funciona" en ARC.

+0

gracias por esto ... solo una pregunta aquí con respecto al objeto creado estáticamente. ¿No deberías liberar ese objeto i dealloc? Siempre estoy confundido acerca de la propiedad de los objetos estáticos. Entonces no deberías tener algo como la versión [[MyClass sharedInstance] versión]; en dealloc? – Abolfoooud

+5

Los Singletons existen desde el momento en que se solicitan hasta que finaliza la aplicación. No se desasignan ni se vuelven a crear instancias. Como no hay ninguna razón para desasignar nada sobre la terminación de la aplicación, no hay ninguna razón para 'liberar' el singleton. Debido a que es poco probable que la destrucción del singleton haya sido probada alguna vez, la implementación del 'dealloc' como se muestra es una medida puramente defensiva para recordarle al pasado que no pensó en el manejo de memoria para esta clase. – bbum

+0

Gracias por la aclaración – Abolfoooud

19
// See Mike Ash "Care and Feeding of Singletons" 
// See Cocoa Samurai "Singletons: You're doing them wrong" 
+(MySingleton *)singleton { 
    static dispatch_once_t pred; 
    static MySingleton *shared = nil; 
    dispatch_once(&pred, ^{ 
     shared = [[MySingleton alloc] init]; 
     shared.someIvar = @"blah"; 
    }); 
    return shared; 
} 

Tenga en cuenta que dispatch_once is not reentrant, por lo que se hace llamar desde el interior del bloque de dispatch_once será un punto muerto del programa.

No intente codificar a la defensiva contra usted mismo. Si no está codificando un marco, trate su clase como normal y luego adhiera el idioma singleton anterior. Piense en el idioma único como un método de conveniencia, no como un rasgo definitorio de su clase. Desea tratar su clase como una clase normal durante la prueba unitaria, por lo que está bien dejar un constructor accesible.

No trate de utilizar allocWithZone:

  • Ignora su argumento y se comporta exactamente igual que alloc. Las zonas de memoria ya no se usan en Objective-C, por lo que allocWithZone: solo se conserva para compatibilidad con el código anterior.
  • No funciona. No se puede aplicar el comportamiento singleton en Objective-C porque siempre se pueden crear más instancias usando NSAllocateObject() y class_createInstance().

Un método de fábrica Singleton siempre devuelve uno de estos tres tipos:

  • id para indicar el tipo de retorno no se conoce totalmente (caso en el que usted está construyendo un grupo de clase).
  • instancetype para indicar que el tipo devuelto es una instancia de la clase adjunta.
  • El nombre de clase en sí (MySingleton en el ejemplo) para mantenerlo simple.

Desde ha etiquetado este IOS, una alternativa a un conjunto unitario está ahorrando el Ivar al delegado de la aplicación y luego usando una macro conveniencia que puede volver a definir si cambia de opinión:

#define coreDataManager() \ 
     ((AppDelegate*)[[UIApplication sharedApplication] delegate]).coreDataManager 
1

Si desea probar su unidad de Singleton también hay que hacerlo de modo que se puede reemplazar con un producto único maqueta y/o restablecerlo a la normal:

@implementation ArticleManager 

static ArticleManager *_sharedInstance = nil; 
static dispatch_once_t once_token = 0; 

+(ArticleManager *)sharedInstance { 
    dispatch_once(&once_token, ^{ 
     if (_sharedInstance == nil) { 
      _sharedInstance = [[ArticleManager alloc] init]; 
     } 
    }); 
    return _sharedInstance; 
} 

+(void)setSharedInstance:(ArticleManager *)instance { 
    once_token = 0; // resets the once_token so dispatch_once will run again 
    _sharedInstance = instance; 
} 

@end 
+0

Paso por cada línea de esa muestra de código en una publicación de blog aquí: http://twobitlabs.com/2013/01/objective-c-singleton-pattern-unit-testing/ – ToddH

Cuestiones relacionadas