¿Cómo implemento un singleton Objective-C que sea compatible con ARC?

Respuestas:

391

Exactamente de la misma manera que (debería) haberlo hecho ya:

+ (instancetype)sharedInstance
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyClass alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}
Nick Forge
fuente
9
Simplemente no hace nada de la gestión de memoria hokey pokey que Apple solía recomendar en developer.apple.com/library/mac/documentation/Cocoa/Conceptual/…
Christopher Pickslay
1
@MakingScienceFictionFact, es posible que desee echar un vistazo a esta publicación
kervich
66
Las staticvariables @David declaradas dentro de un método / función son las mismas que una staticvariable declarada fuera de un método / función, solo son válidas dentro del alcance de ese método / función. Cada ejecución por separado del +sharedInstancemétodo (incluso en subprocesos diferentes) "verá" la misma sharedInstancevariable.
Nick Forge
9
¿Qué pasa si alguien llama a [[MyClass alloc] init]? Eso crearía un nuevo objeto. ¿Cómo podemos evitar esto (aparte de declarar estática MyClass * sharedInstance = nil fuera del método)?
Ricardo Sanchez-Saez
2
Si otro programador se equivoca y llama a init cuando debería haber llamado sharedInstance o similar, es su error. Subvertir los fundamentos y los contratos básicos del lenguaje para evitar que otros cometan errores parece bastante incorrecto. Hay más discusión en boredzo.org/blog/archives/2009-06-17/doing-it-wrong
occulus
8

si desea crear otra instancia según sea necesario. haga esto:

+ (MyClass *)sharedInstance
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyClass alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}

de lo contrario, deberías hacer esto:

+ (id)allocWithZone:(NSZone *)zone
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [super allocWithZone:zone];
    });
    return sharedInstance;
}
DongXu
fuente
1
Verdadero / Falso: ¿El dispatch_once()bit significa que no obtendrá instancias adicionales, incluso en el primer ejemplo ...?
Olie
44
@Olie: Falso, porque el código del cliente puede hacer [[MyClass alloc] init]y omitir el sharedInstanceacceso. DongXu, deberías mirar el artículo Singleton de Peter Hosey . Si va a anular allocWithZone:para evitar que se creen más instancias, también debe anular initpara evitar que la instancia compartida se reinicialice.
jscs
Ok, eso es lo que pensé, de ahí la allocWithZone:versión. Gracias.
Olie
2
Esto rompe completamente el contrato de allocWithZone.
oculto el
1
singleton solo significa "solo un objeto en la memoria a la vez", esto es una cosa, reinicializar es otra cosa.
DongXu
5

Esta es una versión para ARC y no ARC

Cómo utilizar:

MySingletonClass.h

@interface MySingletonClass : NSObject

+(MySingletonClass *)sharedInstance;

@end

MySingletonClass.m

#import "MySingletonClass.h"
#import "SynthesizeSingleton.h"
@implementation MySingletonClass
SYNTHESIZE_SINGLETON_FOR_CLASS(MySingletonClass)
@end
Igor
fuente
2

Este es mi patrón bajo ARC. Satisface el nuevo patrón con GCD y también satisface el antiguo patrón de prevención de creación de instancias de Apple.

@implementation AAA
+ (id)alloc
{
    return  [self allocWithZone:nil];
}
+ (id)allocWithZone:(NSZone *)zone
{
    [self doesNotRecognizeSelector:_cmd];
    abort();
}
+ (instancetype)theController
{
    static AAA* c1  =   nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
    {
        c1  =   [[super allocWithZone:nil] init];

        // For confirm...       
        NSLog(@"%@", NSStringFromClass([c1 class]));    //  Prints AAA
        NSLog(@"%@", @([c1 class] == self));            //  Prints 1

        Class   real_superclass_obj =   class_getSuperclass(self);
        NSLog(@"%@", @(real_superclass_obj == self));   //  Prints 0
    });

    return  c1;
}
@end
eonil
fuente
1
¿No resultará esto en c1ser una instancia de AAAla superclase de '? Es necesario llamar +alloca self, no en super.
Nick Forge
@NickForge superno significa el objeto de superclase . No puede obtener un objeto de superclase Simplemente significa enrutar mensajes a la versión de superclase del método. supertodavía señala selfclase. Si desea obtener un objeto de superclase, necesita obtener funciones de reflexión en tiempo de ejecución.
eonil
@NickForge y el -allocWithZone:método es solo una cadena simple a la función de asignación de tiempo de ejecución para ofrecer un punto de anulación. Entonces, en última instancia, el selfpuntero == objeto de clase actual se pasará al asignador, y finalmente AAAse asignará la instancia.
eonil
tienes razón, olvidé las sutilezas de cómo superfunciona en los métodos de clase.
Nick Forge
Recuerde usar #import <objc / objc-runtime.h>
Ryan Heitner
2

Lea esta respuesta y luego vaya y lea la otra respuesta.

Primero debe saber qué significa un Singleton y cuáles son sus requisitos, si no lo comprende, entonces no entenderá la solución, ¡en absoluto!

Para crear un Singleton con éxito, debe poder hacer lo siguiente 3:

  • Si hubo una condición de carrera , ¡no debemos permitir que se creen varias instancias de su SharedInstance al mismo tiempo!
  • Recuerde y mantenga el valor entre múltiples invocaciones.
  • Créalo solo una vez. Al controlar el punto de entrada.

dispatch_once_tte ayuda a resolver una condición de carrera al permitir que su bloqueo se envíe una sola vez.

Staticle ayuda a "recordar" su valor en cualquier cantidad de invocaciones. ¿Cómo se recuerda? No permite que se vuelva a crear ninguna instancia nueva con ese nombre exacto de su SharedInstance, solo funciona con la que se creó originalmente.

No usar llamadas alloc init(es decir, todavía tenemos alloc initmétodos ya que somos una subclase de NSObject, aunque NO deberíamos usarlos) en nuestra clase sharedInstance, lo logramos usando +(instancetype)sharedInstance, que está limitado a iniciarse solo una vez , independientemente de múltiples intentos de diferentes hilos al mismo tiempo y recuerda su valor.

Algunos de los Singletons del sistema más comunes que vienen con Cocoa son:

  • [UIApplication sharedApplication]
  • [NSUserDefaults standardUserDefaults]
  • [NSFileManager defaultManager]
  • [NSBundle mainBundle]
  • [NSOperations mainQueue]
  • [NSNotificationCenter defaultCenter]

Básicamente, cualquier cosa que deba tener un efecto centralizado deberá seguir algún tipo de patrón de diseño Singleton.

Miel
fuente
1

Alternativamente, Objective-C proporciona el método de inicialización + (vacío) para NSObject y todas sus subclases. Siempre se llama antes que cualquier método de la clase.

Establecí un punto de interrupción en uno una vez en iOS 6 y dispatch_once apareció en los marcos de la pila.

Walt Sellers
fuente
0

Singleton Class: nadie puede crear más de un objeto de clase en ningún caso ni de ninguna manera.

+ (instancetype)sharedInstance
{
    static ClassName *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[ClassName alloc] init];
        // Perform other initialisation...
    });
    return sharedInstance;
}
//    You need need to override init method as well, because developer can call [[MyClass alloc]init] method also. that time also we have to return sharedInstance only. 

-(MyClass)init
{
   return [ClassName sharedInstance];
}
Yogui
fuente
1
Si alguien llama a init, init llamará a sharedInstance, sharedInstance llamará a init, init llamará a sharedInstance por segunda vez y luego se bloqueará. Primero, este es un bucle de recursión infinito. En segundo lugar, la segunda iteración de llamar a dispatch_once se bloqueará porque no se puede volver a llamar desde dentro de dispatch_once.
Chuck Krutsinger
0

Hay dos problemas con la respuesta aceptada, que pueden o no ser relevantes para su propósito.

  1. Si desde el método init, de alguna manera se vuelve a llamar al método sharedInstance (por ejemplo, porque desde allí se construyen otros objetos que usan el singleton), provocará un desbordamiento de la pila.
  2. Para las jerarquías de clases solo hay un singleton (es decir: la primera clase en la jerarquía en la que se llamó al método sharedInstance), en lugar de un singleton por clase concreta en la jerarquía.

El siguiente código se ocupa de estos dos problemas:

+ (instancetype)sharedInstance {
    static id mutex = nil;
    static NSMutableDictionary *instances = nil;

    //Initialize the mutex and instances dictionary in a thread safe manner
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mutex = [NSObject new];
        instances = [NSMutableDictionary new];
    });

    id instance = nil;

    //Now synchronize on the mutex
    //Note: do not synchronize on self, since self may differ depending on which class this method is called on
    @synchronized(mutex) {
        id <NSCopying> key = (id <NSCopying>)self;
        instance = instances[key];
        if (instance == nil) {
            //Break allocation and initialization into two statements to prevent a stack overflow, if init somehow calls the sharedInstance method
            id allocatedInstance = [self alloc];

            //Store the instance into the dictionary, one per concrete class (class acts as key for the dictionary)
            //Do this right after allocation to avoid the stackoverflow problem
            if (allocatedInstance != nil) {
                instances[key] = allocatedInstance;
            }
            instance = [allocatedInstance init];

            //Following code may be overly cautious
            if (instance != allocatedInstance) {
                //Somehow the init method did not return the same instance as the alloc method
                if (instance == nil) {
                    //If init returns nil: immediately remove the instance again
                    [instances removeObjectForKey:key];
                } else {
                    //Else: put the instance in the dictionary instead of the allocatedInstance
                    instances[key] = instance;
                }
            }
        }
    }
    return instance;
}
Werner Altewischer
fuente
-2
#import <Foundation/Foundation.h>

@interface SingleTon : NSObject

@property (nonatomic,strong) NSString *name;
+(SingleTon *) theSingleTon;

@end

#import "SingleTon.h"
@implementation SingleTon

+(SingleTon *) theSingleTon{
    static SingleTon *theSingleTon = nil;

    if (!theSingleTon) {

        theSingleTon = [[super allocWithZone:nil] init
                     ];
    }
    return theSingleTon;
}

+(id)allocWithZone:(struct _NSZone *)zone{

    return [self theSingleTon];
}

-(id)init{

    self = [super init];
    if (self) {
        // Set Variables
        _name = @"Kiran";
    }

    return self;
}

@end

Espero que el código anterior lo ayude.

Kiran
fuente
-2

si necesitas crear singleton en swift,

class var sharedInstance: MyClass {
    struct Singleton {
        static let instance = MyClass()
    }
    return Singleton.instance
}

o

struct Singleton {
    static let sharedInstance = MyClass()
}

class var sharedInstance: MyClass {
    return Singleton.sharedInstance
}

puedes usar de esta manera

let sharedClass = LibraryAPI.sharedInstance
muhammedkasva
fuente