2011-09-01 12 views
16

He leído un par de increíbles recursos en embarazos únicos en Obj-C:¿Cómo Objective-C singleton debería implementar el método init?

  1. SO pregunta: What does your Objective-C singleton look like?
  2. Viernes Q & A: Care and Feeding of Singletons
  3. docs Apple: Creating a Singleton Instance

pero ninguno de estos recursos se dirigió init método concepto explícitamente y siendo aún nov hielo a Obj-C Estoy confundido, ¿cómo debo implementarlo?

Hasta ahora sabemos que tener init privada no es posible en Obj-C, ya que no ofrece verdaderos métodos privados ... así que es posible que el usuario puede llamar [[MyClass alloc] init] en lugar de utilizar mi [MyClass sharedInstance].

¿Cuáles son mis otras opciones? Creo que también debería manejar escenarios de subclases de mi singleton.

Respuesta

25

Bueno, una manera fácil de evitar el init es simplemente no escribir uno para llamarlo a la implementación predeterminada de NSObject (que solo devuelve self). Luego, para su función sharedInstance, defina y llame a una función privada que realice un trabajo tipo init cuando crea una instancia de su singleton. (Esto evita que el usuario vuelva a inicializar accidentalmente su singleton).

¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡ ¡El principal problema es cuando un usuario de tu código llama al alloc! Por esto, yo personalmente recomiendo ruta de Apple de primordial allocWithZone: ...

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

Esto significa que el usuario seguirá recibiendo la instancia singleton, y pueden utilizar erróneamente como si se asignan, y de forma segura liberarlo una vez desde este la asignación personalizada realiza una retención en el singleton. (Nota: alloc llama al allocWithZone: y no necesita ser reemplazado por separado.)

Espero que ayude! Que me haga saber si desea más información ~

EDIT: La expansión de respuesta para proporcionar el ejemplo y más detalles -

Tomando la respuesta de Catfish_Man en consideración, es menudo no es importante para crear un producto único a prueba de balas, y en su lugar, solo escriba algunos comentarios razonables en sus encabezados/documentación y ponga en assert.

Sin embargo, en mi caso, quería un singleton de carga diferida seguro para subprocesos, es decir, no se asigna hasta que se necesita usar, en lugar de asignarse automáticamente en el inicio de la aplicación. Después de aprender cómo hacerlo de manera segura, pensé que también podría seguir todo el camino con eso.

editar # 2: ahora uso GCD de dispatch_once(...) de un enfoque seguro para subprocesos de la asignación de un objeto singleton sólo una vez para toda la vida de una aplicación. Ver Apple Docs: GCD dispatch_once. También sigue añadir allocWithZone: poco anulación del viejo ejemplo Singleton de Apple, y añadí un init privado llamado singletonInit para evitar que accidentalmente se llama varias veces:

//Hidden/Private initialization 
-(void)singletonInit 
{ 
    //your init code goes here 
} 

static HSCloudManager * sharedInstance = nil; 

+ (HSCloudManager *) sharedManager {         
    static dispatch_once_t dispatchOncePredicate = 0;     
    dispatch_once(&dispatchOncePredicate, ^{       
     sharedInstance = [[super allocWithZone:NULL] init];   
     [sharedInstance singletonInit];//Only place you should call singletonInit 
    });                 
    return sharedInstance;              
} 

+ (id) allocWithZone:(NSZone *)zone { 
    //If coder misunderstands this is a singleton, behave properly with 
    // ref count +1 on alloc anyway, and still return singleton! 
    return [[HSCloudManager sharedManager] retain]; 
} 

HSCloudManager subclases NSObject, y no anula init dejando sólo el valor por defecto implementación en NSObject, que según la documentación de Apple solo devuelve auto. Esto significa que [[HSCloudManager alloc] init] es lo mismo que [[[HSCloud Manager sharedManager] retain] self], lo que lo hace seguro tanto para usuarios confundidos como para aplicaciones de subprocesos múltiples como un singleton de carga lenta.

En cuanto a su preocupación sobre la subclasificación de su singleton por parte del usuario, yo diría que simplemente comente/documente claramente. ¡Cualquiera que esté subclasificando ciegamente sin leer en la clase es pidiendo por dolor!

editar # 3: Para compatibilidad ARC, basta con retirar la porción de retener de la anulación allocWithZone:, pero mantener la anulación.

+0

@yAaak, parece bastante justo , pero me siento como en un ciclo infinito y necesito digerirlo;) ¿Cómo me aseguro de que el proceso de creación de dicho singleton a través de un método privado sea seguro para subprocesos? Otro problema: si sigo la idea de 'allocWithZone' de Apple, se llamará a' init' de NSObject (¿simplemente devuelve 'self' o algo más?) ... y de nuevo el usuario intenta crear una instancia con' alloc' y ' init' ¿cambia algo la inicialización de mis propiedades/ivars y NSObject obtiene 'init' nuevamente? – matm

+0

yAak, tienes razón: enfoque sensato que estás sugiriendo que es suficiente por ahora. Jugaré con singletons un poco más usando tu código :) Creo que tu respuesta es bastante extensa y tiene en cuenta la contra opinión de Catfish_man, así que la acepto. ¡Gracias de nuevo! – matm

+0

Sugiero dispatch_once() en lugar de OSAtomic *. Menos quisquilloso, igual de rápido o más rápido. –

2

El método init no debe verse afectado. Será lo mismo en una clase singleton que en una clase regular. Es posible que desee sobrescribir allocWithZone: (que se llama por alloc) para evitar crear más de una instancia de su clase.

3

Honestamente? Toda la moda de escribir clases de singleton a prueba de balas me parece bastante exagerada. Si en verdad está tan preocupado por eso, simplemente aplique assert (sharedInstance == nil) allí antes de asignarlo la primera vez. De esa forma, se bloqueará si alguien lo usa mal, de inmediato haciéndoles saber que es un idiota.

+2

Parcialmente estoy de acuerdo, pero otra mitad de mí dice: ¿por qué transigir? Sin embargo, voy por lo que puedo lograr más comentarios claros en el encabezado/documentos. – matm

-1

Prueba este

+(id)allocWithZone:(struct _NSZone *)zone{ 
    static dispatch_once_t onceToken_alloc; 
    static id __alloc_instance; 
    dispatch_once(&onceToken_alloc, ^{ 
     __alloc_instance = [super allocWithZone:zone]; 
    }); 
    return __alloc_instance; 
} 

-(id)init{ 
    static id __instance; 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     __instance = [super init]; 
    }); 
    return __instance; 
} 
+0

Esto está asignando una nueva instancia en lugar de devolver la instancia de singleton. – ebi

+0

No, no es así. – dotAramis

+0

Lo probé y tiene razón, pero un singleton debería implementar un método de clase para el acceso en lugar de requerir que el consumidor llame a alloc para obtener la instancia. – ebi

0

Para hacer que el init/nuevos métodos disponibles para las personas que llaman de la clase Singleton que podría utilizar la macro NS_UNAVAILABLE en el archivo de cabecera:

- (id)init NS_UNAVAILABLE; 
+ (id)new NS_UNAVAILABLE; 

+ (instancetype)sharedInstance; 
Cuestiones relacionadas