Crear singleton utilizando dispatch_once de GCD en Objective C

Si puedes apuntar a iOS 4.0 o superior

Usando GCD, ¿es la mejor manera de crear singleton en Objective C (thread safe)?

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

Esta es una manera perfectamente aceptable y segura para crear una instancia de su clase. Puede que técnicamente no sea un “singleton” (en el que solo puede haber 1 de estos objetos), pero siempre que solo use el método [Foo sharedFoo] para acceder al objeto, esto es suficiente.

instancia

instancetype es solo una de las muchas extensiones de lenguaje de Objective-C , y cada nueva versión agrega más.

Conócelo, ámalo.

Y tómenlo como ejemplo de cómo prestar atención a los detalles de bajo nivel puede proporcionarle información sobre nuevas y poderosas formas de transformar Objective-C.

Consulte aquí: instancetype


 + (instancetype)sharedInstance { static dispatch_once_t once; static id sharedInstance; dispatch_once(&once, ^ { sharedInstance = [self new]; }); return sharedInstance; } 

 + (Class*)sharedInstance { static dispatch_once_t once; static Class *sharedInstance; dispatch_once(&once, ^ { sharedInstance = [self new]; }); return sharedInstance; } 

MySingleton.h

 @interface MySingleton : NSObject +(instancetype)sharedInstance; +(instancetype)alloc __attribute__((unavailable("alloc not available, call sharedInstance instead"))); -(instancetype)init __attribute__((unavailable("init not available, call sharedInstance instead"))); +(instancetype)new __attribute__((unavailable("new not available, call sharedInstance instead"))); -(instancetype)copy __attribute__((unavailable("copy not available, call sharedInstance instead"))); @end 

MySingleton.m

 @implementation MySingleton +(instancetype)sharedInstance { static dispatch_once_t pred; static id shared = nil; dispatch_once(&pred, ^{ shared = [[super alloc] initUniqueInstance]; }); return shared; } -(instancetype)initUniqueInstance { return [super init]; } @end 

Dave está en lo correcto, eso está perfectamente bien. Puede consultar los documentos de Apple sobre cómo crear un singleton para obtener consejos sobre cómo implementar algunos de los otros métodos para garantizar que solo se pueda crear uno si las clases eligen NO usar el método sharedFoo.

Puede evitar que la clase se asigne con sobrescribir el método alloc.

 @implementation MyClass static BOOL useinside = NO; static id _sharedObject = nil; +(id) alloc { if (!useinside) { @throw [NSException exceptionWithName:@"Singleton Vialotaion" reason:@"You are violating the singleton class usage. Please call +sharedInstance method" userInfo:nil]; } else { return [super alloc]; } } +(id)sharedInstance { static dispatch_once_t p = 0; dispatch_once(&p, ^{ useinside = YES; _sharedObject = [[MyClass alloc] init]; useinside = NO; }); // returns the same object each time return _sharedObject; } 

Si quiere asegurarse de que [[MyClass alloc] init] devuelve el mismo objeto que sharedInstance (no es necesario en mi opinión, pero algunos lo quieren), eso se puede hacer de manera fácil y segura usando un segundo dispatch_once:

 - (instancetype)init { static dispatch_once_t once; static Class *sharedInstance; dispatch_once(&once, ^ { // Your normal init code goes here. sharedInstance = self; }); return sharedInstance; } 

Esto permite que cualquier combinación de [[MyClass alloc] init] y [MyClass sharedInstance] devuelva el mismo objeto; [MyClass sharedInstance] sería un poco más eficiente. Cómo funciona: [MyClass sharedInstance] llamará a [[MyClass alloc] init] una vez. Otro código podría llamarlo también, cualquier cantidad de veces. La primera persona que llama a init realizará la inicialización “normal” y guardará el objeto singleton en el método init. Cualquier llamada posterior a init ignorará por completo qué alloc devolvió y devolverá la misma sharedInstance; el resultado de alloc será desasignado.

El método + sharedInstance funcionará como siempre. Si no es el primer interlocutor que llama a [[MyClass alloc] init], el resultado de init no es el resultado de la llamada de alloc, pero eso está bien.

Para crear singleton seguro para subprocesos, puede hacer esto:

 @interface SomeManager : NSObject + (id)sharedManager; @end /* thread safe */ @implementation SomeManager static id sharedManager = nil; + (void)initialize { if (self == [SomeManager class]) { sharedManager = [[self alloc] init]; } } + (id)sharedManager { return sharedManager; } @end 

y este blog explica singleton muy bien singletons en objc / cocoa

 //Create Singleton +( instancetype )defaultDBManager { static dispatch_once_t onceToken = 0; __strong static id _sharedObject = nil; dispatch_once(&onceToken, ^{ _sharedObject = [[self alloc] init]; }); return _sharedObject; } //In it method -(instancetype)init { self = [super init]; if(self) { //Do your custom initialization } return self; } 

Usted pregunta si esta es la “mejor manera de crear singleton”.

Algunas reflexiones

  1. Primero, sí, esta es una solución segura para subprocesos. Este patrón dispatch_once es la forma moderna e inamovible de generar singletons en Objective-C. No hay preocupaciones allí.

  2. Usted preguntó, sin embargo, si esta es la “mejor” forma de hacerlo. Sin embargo, se debe reconocer que el tipo de instancetype y [[self alloc] init] son potencialmente engañosos cuando se usan junto con singletons.

    El beneficio del tipo de instancetype es que es una forma inequívoca de declarar que la clase puede ser subclasificada sin recurrir a un tipo de id , como teníamos que hacer en antaño.

    Pero la static en este método presenta desafíos de subclases. ¿Qué ImageCache si los singletons de ImageCache y BlobCache son ambas subclases de una superclase de Cache sin implementar su propio método sharedCache ?

     ImageCache *imageCache = [ImageCache sharedCache]; // fine BlobCache *blobCache = [BlobCache sharedCache]; // error; this will return the aforementioned ImageCache!!! 

    Para que esto funcione, debes asegurarte de que las subclases implementen su propio sharedInstance (o como lo llames para tu clase en particular).

    En sharedInstance , su sharedInstance original parece que admitirá subclases, pero no lo hará. Si tiene la intención de admitir la creación de subclases, al menos incluya documentación que advierta a los futuros desarrolladores que deben anular este método.

  3. Para una mejor interoperabilidad con Swift, probablemente desee definir que se trata de una propiedad, no un método de clase, por ejemplo:

     @interface Foo : NSObject @property (class, readonly, strong) Foo *sharedFoo; @end 

    Luego, puede continuar y escribir un getter para esta propiedad (la implementación usaría el patrón dispatch_once que sugirió):

     + (Foo *)sharedFoo { ... } 

    El beneficio de esto es que si un usuario de Swift va a usarlo, haría algo como:

     let foo = Foo.shared 

    Tenga en cuenta que no hay () , porque lo implementamos como una propiedad. Al iniciar Swift 3, así es como generalmente se accede a los singletons. Entonces, definirlo como una propiedad ayuda a facilitar esa interoperabilidad.

    Por otro lado, si nos fijamos en cómo Apple está definiendo sus singletons, este es el patrón que han adoptado, por ejemplo, su singleton NSURLSession se define de la siguiente manera:

     @property (class, readonly, strong) NSURLSession *sharedSession; 
  4. Otra consideración muy pequeña de interoperabilidad Swift fue el nombre del singleton. Lo mejor es que pueda incorporar el nombre del tipo, en lugar de sharedInstance . Por ejemplo, si la clase era Foo , podría definir la propiedad singleton como sharedFoo . O si la clase era DatabaseManager , puede llamar a la propiedad sharedManager . Entonces los usuarios de Swift podrían hacer:

     let foo = Foo.shared let manager = DatabaseManager.shared 

    Claramente, si realmente desea usar sharedInstance , siempre puede declarar el nombre de Swift si lo desea:

     @property (class, readonly, strong) Foo* sharedInstance NS_SWIFT_NAME(shared); 

    Claramente, al escribir el código Objective-C, no deberíamos permitir que la interoperabilidad Swift supere otras consideraciones de diseño, pero aún así, si podemos escribir código que soporte con gracia ambos idiomas, es preferible.

  5. Estoy de acuerdo con otros que señalan que si quieres que este sea un singleton verdadero donde los desarrolladores no pueden / no deben (accidentalmente) instanciar sus propias instancias, el calificador unavailable en init y new es prudente.