Come posso implementare un singleton Objective-C compatibile con ARC?


172

Come posso convertire (o creare) una classe singleton che compila e si comporta correttamente quando utilizzo il conteggio dei riferimenti automatici (ARC) in Xcode 4.2?


1
Di recente ho trovato un articolo di Matt Galloway che approfondisce Singletons sia per ARC che per ambienti di gestione manuale della memoria. galloway.me.uk/tutorials/singleton-classes
cescofry

Risposte:


391

Esattamente allo stesso modo in cui avresti dovuto (già) farlo:

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

9
Semplicemente non esegui nessuna delle funzioni di gestione della memoria che Apple usava raccomandare in developer.apple.com/library/mac/documentation/Cocoa/Conceptual/…
Christopher Pickslay,

1
@MakingScienceFictionFact, potresti dare un'occhiata a questo post
kervich

6
Le staticvariabili @David dichiarate all'interno di un metodo / funzione sono le stesse di una staticvariabile dichiarata al di fuori di un metodo / funzione, sono valide solo nell'ambito di tale metodo / funzione. Ogni esecuzione separata attraverso il +sharedInstancemetodo (anche su thread diversi) "vedrà" la stessa sharedInstancevariabile.
Nick Forge,

9
Che dire se qualcuno chiama [[MyClass alloc] init]? Ciò creerebbe un nuovo oggetto. Come possiamo evitarlo (oltre a dichiarare MyClass statica * sharedInstance = nil al di fuori del metodo).
Ricardo Sanchez-Saez,

2
Se un altro programmatore sbaglia e chiama init quando avrebbero dovuto chiamare sharedInstance o simili, è il loro errore. Sovvertire i fondamenti e i contratti di base della lingua per impedire ad altri che potrebbero commettere errori sembra abbastanza sbagliato. C'è più discussione su boredzo.org/blog/archives/2009-06-17/doing-it-wrong
occulus

8

se si desidera creare un'altra istanza secondo necessità.

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

altrimenti, dovresti fare questo:

+ (id)allocWithZone:(NSZone *)zone
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [super allocWithZone:zone];
    });
    return sharedInstance;
}

1
Vero / Falso: il dispatch_once()bit indica che non otterrai ulteriori istanze, anche nel primo esempio ...?
Olie,

4
@Olie: False, perché il codice client può fare [[MyClass alloc] init]e bypassare l' sharedInstanceaccesso. DongXu, dovresti guardare l'articolo Singleton di Peter Hosey . Se si intende eseguire l'override allocWithZone:per impedire la creazione di più istanze, è necessario eseguire initl' override per impedire la reinizializzazione dell'istanza condivisa.
jscs,

Ok, è quello che pensavo, da qui la allocWithZone:versione. Grazie.
Olie,

2
Questo rompe completamente il contratto di allocWithZone.
occulus

1
singleton significa solo "un solo oggetto in memoria in qualsiasi momento", questa è una cosa, essere reinizializzati è un'altra cosa.
DongXu,

5

Questa è una versione per ARC e non ARC

Come usare:

MySingletonClass.h

@interface MySingletonClass : NSObject

+(MySingletonClass *)sharedInstance;

@end

MySingletonClass.m

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

2

Questo è il mio modello sotto ARC. Soddisfa il nuovo modello utilizzando GCD e soddisfa anche il vecchio modello di prevenzione delle istanze di 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

1
Questo risultato non c1sarà un'istanza della AAAsuperclasse? È necessario chiamare +allocil self, non su super.
Nick Forge,

@NickForge supernon significa l'oggetto superclasse . Non è possibile ottenere oggetti di classe superiore Significa semplicemente instradare i messaggi alla versione di classe classe del metodo. superindica ancora la selfclasse. Se si desidera ottenere un oggetto di classe superiore, è necessario ottenere le funzioni di riflessione del runtime.
eonil,

@NickForge E il -allocWithZone:metodo è solo una semplice catena per la funzione di allocazione del runtime per offrire un punto di esclusione. Quindi, alla fine, selfpointer == l'oggetto classe corrente verrà passato all'allocatore e infine AAAverrà allocata l'istanza.
eonil,

hai ragione, avevo dimenticato le sottigliezze di come superfunzionano i metodi di classe.
Nick Forge,

Ricorda di usare #import <objc / objc-runtime.h>
Ryan Heitner,

2

Leggi questa risposta e poi vai a leggere l'altra risposta.

Devi prima sapere cosa significa Singleton e quali sono i suoi requisiti, se non lo capisci, allora non capirai affatto la soluzione!

Per creare correttamente un Singleton devi essere in grado di eseguire le seguenti 3:

  • In presenza di una race condition , non dobbiamo consentire la creazione contemporanea di più istanze di SharedInstance!
  • Ricorda e mantieni il valore tra più invocazioni.
  • Crealo solo una volta. Controllando il punto di ingresso.

dispatch_once_tti aiuta a risolvere una condizione di gara permettendo che il suo blocco venga spedito una sola volta.

Staticti aiuta a "ricordare" il suo valore su qualsiasi numero di invocazioni. Come si ricorda? Non consente di creare nuovamente alcuna nuova istanza con lo stesso nome di sharedInstance, ma funziona solo con quella creata originariamente.

Non usando la chiamata alloc init(ovvero disponiamo ancora di alloc initmetodi poiché siamo una sottoclasse NSObject, sebbene NON dovremmo usarli) sulla nostra classe sharedInstance, lo otteniamo utilizzando +(instancetype)sharedInstance, che è destinato a essere avviato solo una volta , indipendentemente da più tentativi da thread diversi allo stesso tempo e ricordare il suo valore.

Alcuni dei Singleton di sistema più comuni forniti con il cacao stesso sono:

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

Fondamentalmente tutto ciò che dovrebbe avere un effetto centralizzato dovrebbe seguire una sorta di modello di progettazione Singleton.


1

In alternativa, Objective-C fornisce il metodo + (void) di inizializzazione per NSObject e tutte le sue sottoclassi. Viene sempre chiamato prima di qualsiasi metodo della classe.

Ho impostato un breakpoint in uno una volta in iOS 6 e dispatch_once è apparso nei frame dello stack.


0

Classe Singleton: nessuno può creare più di un oggetto di classe in nessun caso o in alcun modo.

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

1
Se qualcuno chiama init, init chiamerà sharedInstance, sharedInstance chiamerà init, init chiamerà sharedInstance una seconda volta, quindi andrà in crash! Innanzitutto, questo è un ciclo di ricorsione infinito. In secondo luogo, la seconda iterazione della chiamata dispatch_once andrà in crash perché non può essere richiamata nuovamente da dispatch_once.
Chuck Krutsinger,

0

Esistono due problemi con la risposta accettata, che possono essere o non essere pertinenti ai tuoi scopi.

  1. Se dal metodo init, in qualche modo viene chiamato di nuovo il metodo sharedInstance (ad es. Perché da lì sono costruiti altri oggetti che usano il singleton) causerà un overflow dello stack.
  2. Per le gerarchie di classi esiste un solo singleton (vale a dire: la prima classe nella gerarchia su cui è stato chiamato il metodo sharedInstance), invece di un singleton per classe concreta nella gerarchia.

Il codice seguente si occupa di entrambi questi problemi:

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

-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

Spero che il codice sopra lo aiuti.


-2

se devi creare singleton in rapido,

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
}

puoi usare in questo modo

let sharedClass = LibraryAPI.sharedInstance
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.