Cree singleton usando el despacho_de_GCD en Objective-C


341

Si puede apuntar a iOS 4.0 o superior

Usando GCD, ¿es la mejor manera de crear singleton en Objective-C (hilo seguro)?

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

2
¿Hay alguna manera de evitar que los usuarios de la clase llamen alloc / copy?
Nicolas Miari

3
dispatch_once_t y dispatch_once parecen haberse introducido en 4.0, no 4.1 (ver: developer.apple.com/library/ios/#documentation/Performance/… )
Ben Flynn

1
Este método se vuelve problemático si init requiere el uso del objeto singleton. El código de Matt Gallagher me ha funcionado en más de unas pocas ocasiones. cocoawithlove.com/2008/11/…
greg

1
Sé que es intrascendente en este ejemplo; pero por qué la gente no usa 'nuevo' más. dispatch_once (& once, ^ {sharedInstance = [self new];} se ve un poco más ordenado. Es equivalente a alloc + init.
Chris Hatton

3
Asegúrese de comenzar a usar el tipo de retorno instancetype. La finalización del código es mucho mejor cuando se usa eso en lugar de id.
Mr Rogers

Respuestas:


215

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 sentido de que solo puede haber 1 de estos objetos), pero siempre que solo use el [Foo sharedFoo]método para acceder al objeto, esto es lo suficientemente bueno.


44
¿Cómo lo liberas?
samvermette el

65
@Samvermette no lo haces. El punto de un singleton es que siempre existirá. por lo tanto, no lo libera y la memoria se recupera con las salidas del proceso.
Dave DeLong

66
@Dave DeLong: En mi opinión, el propósito de tener singleton no es una certeza de su inmortalidad, sino la certeza de que tenemos una instancia. ¿Qué pasa si ese singleton disminuye un semáforo? No se puede decir arbitrariamente que siempre existirá.
jacekmigacz

44
@hooleyhoop Sí, en su documentación . "Si se llama simultáneamente desde varios subprocesos, esta función espera sincrónicamente hasta que el bloque se haya completado".
Kevin

3
@ WalterMartinVargas-Pena, la fuerte referencia está en manos de la variable estática
Dave DeLong

36

tipo de instancia

instancetypees solo una de las muchas extensiones de idioma Objective-C, y se agregan más con cada nueva versión.

Conócelo, ámalo.

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

Consulte aquí: tipo de instancia


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

44
Consejo increíble, gracias! tipo de instancia es una palabra clave contextual que se puede usar como un tipo de resultado para indicar que un método devuelve un tipo de resultado relacionado. ... Con tipo de instancia, el compilador inferirá correctamente el tipo.
Fattie

1
No me queda claro qué significan los dos fragmentos aquí, ¿son equivalentes entre sí? ¿Uno es preferible al otro? Sería bueno si el autor puede agregar algunas explicaciones para esto.
galactica el

33

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

¿Cómo no está disponible el init? ¿No está al menos disponible para uno init?
Miel

2
Singleton debería tener solo un punto de acceso. Y este punto es compartidoInstance. Si tenemos un método init en el archivo * .h, puede crear otra instancia singleton. Esto contradice la definición de un singleton.
Sergey Petruk

1
@ asma22 __attribute __ ((no disponible ()) hace que no esté disponible para usar estos métodos. Si otro programador quiere usar el método marcado como no disponible, obtiene un error
Sergey Petruk

1
Me entiendo completamente y estoy feliz de haber aprendido algo nuevo, no hay nada malo con tu respuesta, solo puede ser un poco confuso para los novatos ...
Cariño

1
Esto solo funciona para MySingleton, por ejemplo, en que MySingleton.mestoy llamando[super alloc]
Sergey Petruk

6

Puede evitar que la clase se asigne sobrescribiendo el método de asignación.

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

1
Esto responde a mi pregunta en los comentarios anteriores. No es que sea demasiado para la programación defensiva, pero ...
Nicolas Miari

5

Dave tiene razón, eso está perfectamente bien. Es posible que desee consultar los documentos de Apple sobre cómo crear un singleton para obtener consejos sobre la implementación de algunos de los otros métodos para garantizar que solo se pueda crear uno si las clases eligen NO usar el método sharedFoo.


8
eh ... ese no es el mejor ejemplo de crear un singleton. No es necesario anular los métodos de administración de memoria.
Dave DeLong

19
Esto es completamente inválido usando ARC.
logancautrell 01 de

El documento citado ha sido retirado desde entonces. Además, las respuestas que son únicamente enlaces a contenido externo son generalmente respuestas SO deficientes. Como mínimo, extraiga porciones relevantes dentro de su respuesta. No te molestes aquí a menos que quieras guardar la vieja forma para la posteridad.
toolbear

4

Si desea asegurarse de que [[MyClass alloc] init] devuelve el mismo objeto que sharedInstance (no es necesario en mi opinión, pero algunas personas lo quieren), eso se puede hacer de manera muy 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á [[MyClass alloc] init] una vez. Otro código podría llamarlo también, cualquier cantidad de veces. La primera persona que llama a init hará la inicialización "normal" y almacenará el objeto singleton en el método init. Cualquier llamada posterior a init ignorará por completo qué asignación devolvió y devolverá la misma SharedInstance; El resultado de alloc será desasignado.

El método + sharedInstance funcionará como siempre. Si no es la primera persona que llama en llamar a [[MyClass alloc] init], entonces el resultado de init no es el resultado de la llamada de asignación, pero eso está bien.


2

Pregunta si esta es la "mejor manera de crear singleton".

Algunas reflexiones:

  1. Primero, sí, esta es una solución segura para subprocesos. Este dispatch_oncepatrón es la forma moderna y segura de subprocesos para generar tonos únicos en Objective-C. No te preocupes allí.

  2. Sin embargo, usted preguntó si esta es la "mejor" forma de hacerlo. Sin embargo, uno debería reconocer que el instancetypey [[self alloc] init]es potencialmente engañoso cuando se usa junto con singletons.

    El beneficio instancetypees que es una forma inequívoca de declarar que la clase puede subclasificarse sin recurrir a un tipo de id, como teníamos que hacer en el pasado.

    Pero staticen este método presenta desafíos de subclases. ¿Qué pasaría si ImageCachey los BlobCachesingletons fueran ambas subclases de una Cachesuperclase sin implementar su propio sharedCachemétodo?

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

    Para que esto funcione, debe asegurarse de que las subclases implementen su propio sharedInstancemétodo (o como lo llame para su clase particular).

    En pocas palabras, sharedInstance parece que su original admitirá subclases, pero no lo hará. Si tiene la intención de admitir 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 esto como 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 captador para esta propiedad (la implementación usaría el dispatch_oncepatrón 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. Iniciando Swift 3, así es como generalmente se accede a los singletons. Por lo tanto, definirlo como una propiedad ayuda a facilitar esa interoperabilidad.

    Además, si observa cómo Apple está definiendo sus singletons, este es el patrón que han adoptado, por ejemplo, su NSURLSessionsingleton se define de la siguiente manera:

    @property (class, readonly, strong) NSURLSession *sharedSession;
  4. Otra consideración de interoperabilidad Swift muy menor fue el nombre del singleton. Es mejor si puede 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 fuera DatabaseManager, podría 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 desea:

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

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

  5. Estoy de acuerdo con otros que señalan que si quieres que esto sea un verdadero singleton donde los desarrolladores no pueden / no deben (accidentalmente) crear instancias de sus propias instancias, el unavailablecalificador está activado inity newes prudente.


0

Para crear singleton seguro para subprocesos puede hacer lo siguiente:

@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


se está vinculando a un artículo muy antiguo mientras OP solicita características sobre la implementación más moderna.
vikingosegundo

1
La pregunta es sobre una implementación específica. Simplemente publicas otra implementación. Ahí, ni siquiera intentas responder la pregunta.
vikingosegundo

1
@vikingosegundo El autor de la pregunta pregunta si el GCD es la mejor manera de crear un singleton seguro para subprocesos, mi respuesta da otra opción.
Hancock_Xu

el autor de la pregunta pregunta si cierta implementación es segura para subprocesos. No está pidiendo opciones.
vikingosegundo

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

0
@interface className : NSObject{
+(className*)SingleTonShare;
}

@implementation className

+(className*)SingleTonShare{

static className* sharedObj = nil;
static dispatch_once_t once = 0;
dispatch_once(&once, ^{

if (sharedObj == nil){
    sharedObj = [[className alloc] init];
}
  });
     return sharedObj;
}
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.