Domanda

Il mio metodo di accesso singleton è in genere una variante di:

static MyClass *gInstance = NULL;

+ (MyClass *)instance
{
    @synchronized(self)
    {
        if (gInstance == NULL)
            gInstance = [[self alloc] init];
    }

    return(gInstance);
}

Cosa potrei fare per migliorare questo?

È stato utile?

Soluzione

Un'altra opzione è quella di utilizzare il metodo + (void) initialize . Dalla documentazione:

  

Il runtime invia inizializza a ciascuna classe in un programma esattamente una volta appena prima che la classe, o qualsiasi classe che eredita da essa, riceva il suo primo messaggio dall'interno del programma. (Pertanto il metodo non può mai essere invocato se la classe non viene utilizzata.) Il runtime invia il messaggio inizializza alle classi in modo thread-safe. Le superclass ricevono questo messaggio prima delle loro sottoclassi.

Quindi potresti fare qualcosa di simile a questo:

static MySingleton *sharedSingleton;

+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        sharedSingleton = [[MySingleton alloc] init];
    }
}

Altri suggerimenti

@interface MySingleton : NSObject
{
}

+ (MySingleton *)sharedSingleton;
@end

@implementation MySingleton

+ (MySingleton *)sharedSingleton
{
  static MySingleton *sharedSingleton;

  @synchronized(self)
  {
    if (!sharedSingleton)
      sharedSingleton = [[MySingleton alloc] init];

    return sharedSingleton;
  }
}

@end

[Fonte]

Per la mia altra risposta di seguito, penso che dovresti fare:

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

Poiché Kendall ha pubblicato un thread sicuro singleton che tenta di evitare i costi di blocco, ho pensato di lanciarne uno anche io:

#import <libkern/OSAtomic.h>

static void * volatile sharedInstance = nil;                                                

+ (className *) sharedInstance {                                                                    
  while (!sharedInstance) {                                                                          
    className *temp = [[self alloc] init];                                                                 
    if(!OSAtomicCompareAndSwapPtrBarrier(0x0, temp, &sharedInstance)) {
      [temp release];                                                                                   
    }                                                                                                    
  }                                                                                                        
  return sharedInstance;                                                                        
}

Ok, lasciami spiegare come funziona:

  1. Caso veloce: nell'esecuzione normale sharedInstance è già stato impostato, quindi il ciclo while non viene mai eseguito e la funzione ritorna dopo aver semplicemente testato l'esistenza della variabile ;

  2. Caso lento: se sharedInstance non esiste, un'istanza viene allocata e copiata in essa usando un confronto e uno scambio ('CAS');

  3. Caso conteso: se due thread tentano entrambi di chiamare sharedInstance contemporaneamente AND sharedInstance non esiste nel allo stesso tempo, entrambi inizializzeranno nuove istanze del singleton e tenteranno di posizionarlo in CAS. Qualunque vince immediatamente il CAS restituisce, qualunque cosa perdi rilascia l'istanza che ha appena assegnato e restituisce (ora impostato) sharedInstance . Il singolo OSAtomicCompareAndSwapPtrBarrier funge sia da barriera di scrittura per il thread di impostazione che da barriera di lettura dal thread di test.

static MyClass *sharedInst = nil;

+ (id)sharedInstance
{
    @synchronize( self ) {
        if ( sharedInst == nil ) {
            /* sharedInst set up in init */
            [[self alloc] init];
        }
    }
    return sharedInst;
}

- (id)init
{
    if ( sharedInst != nil ) {
        [NSException raise:NSInternalInconsistencyException
            format:@"[%@ %@] cannot be called; use +[%@ %@] instead"],
            NSStringFromClass([self class]), NSStringFromSelector(_cmd), 
            NSStringFromClass([self class]),
            NSStringFromSelector(@selector(sharedInstance)"];
    } else if ( self = [super init] ) {
        sharedInst = self;
        /* Whatever class specific here */
    }
    return sharedInst;
}

/* These probably do nothing in
   a GC app.  Keeps singleton
   as an actual singleton in a
   non CG app
*/
- (NSUInteger)retainCount
{
    return NSUIntegerMax;
}

- (oneway void)release
{
}

- (id)retain
{
    return sharedInst;
}

- (id)autorelease
{
    return sharedInst;
}

Modifica: questa implementazione è obsoleta con ARC. Dai un'occhiata a How devo implementare un singleton Objective-C compatibile con ARC? per una corretta implementazione.

Tutte le implementazioni di inizializzazione che ho letto in altre risposte condividono un errore comune.

+ (void) initialize {
  _instance = [[MySingletonClass alloc] init] // <----- Wrong!
}

+ (void) initialize {
  if (self == [MySingletonClass class]){ // <----- Correct!
      _instance = [[MySingletonClass alloc] init] 
  }
}

La documentazione di Apple consiglia di verificare il tipo di classe nel blocco di inizializzazione. Perché le sottoclassi chiamano l'inizializzazione per impostazione predefinita. Esiste un caso non ovvio in cui le sottoclassi possono essere create indirettamente tramite KVO. Perché se aggiungi la seguente riga in un'altra classe:

[[MySingletonClass getInstance] addObserver:self forKeyPath:@"foo" options:0 context:nil]

Objective-C creerà implicitamente una sottoclasse di MySingletonClass risultante in una seconda attivazione di + inizializza .

Potresti pensare che dovresti controllare implicitamente l'inizializzazione duplicata nel tuo blocco init in quanto tale:

- (id) init { <----- Wrong!
   if (_instance != nil) {
      // Some hack
   }
   else {
      // Do stuff
   }
  return self;
}

Ma ti sparerai al piede; o peggio, dare a un altro sviluppatore l'opportunità di spararsi ai piedi.

- (id) init { <----- Correct!
   NSAssert(_instance == nil, @"Duplication initialization of singleton");
   self = [super init];
   if (self){
      // Do stuff
   }
   return self;
}

TL; DR, ecco la mia implementazione

@implementation MySingletonClass
static MySingletonClass * _instance;
+ (void) initialize {
   if (self == [MySingletonClass class]){
      _instance = [[MySingletonClass alloc] init];
   }
}

- (id) init {
   ZAssert (_instance == nil, @"Duplication initialization of singleton");
   self = [super init];
   if (self) {
      // Initialization
   }
   return self;
}

+ (id) getInstance {
   return _instance;
}
@end

(Sostituisci ZAssert con la nostra macro di asserzioni; o solo NSAssert.)

Una spiegazione approfondita del codice macro di Singleton è sul blog Cocoa With Love

http://cocoawithlove.com/2008/11 /singletons-appdelegates-and-top-level.html.

Ho una variante interessante su sharedInstance che è thread-safe, ma non si blocca dopo l'inizializzazione. Non ne sono ancora abbastanza sicuro da modificare la risposta migliore come richiesto, ma la presento per ulteriori discussioni:

// Volatile to make sure we are not foiled by CPU caches
static volatile ALBackendRequestManager *sharedInstance;

// There's no need to call this directly, as method swizzling in sharedInstance
// means this will get called after the singleton is initialized.
+ (MySingleton *)simpleSharedInstance
{
    return (MySingleton *)sharedInstance;
}

+ (MySingleton*)sharedInstance
{
    @synchronized(self)
    {
        if (sharedInstance == nil)
        {
            sharedInstance = [[MySingleton alloc] init];
            // Replace expensive thread-safe method 
            // with the simpler one that just returns the allocated instance.
            SEL origSel = @selector(sharedInstance);
            SEL newSel = @selector(simpleSharedInstance);
            Method origMethod = class_getClassMethod(self, origSel);
            Method newMethod = class_getClassMethod(self, newSel);
            method_exchangeImplementations(origMethod, newMethod);
        }
    }
    return (MySingleton *)sharedInstance;
}

Risposta breve: favoloso.

Risposta lunga: qualcosa come ....

static SomeSingleton *instance = NULL;

@implementation SomeSingleton

+ (id) instance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (instance == NULL){
            instance = [[super allocWithZone:NULL] init];
        }
    });
    return instance;
}

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

- (id) copyWithZone:(NSZone *)paramZone {
    return self;
}

- (id) autorelease {
    return self;
}

- (NSUInteger) retainCount {
    return NSUIntegerMax;
}

- (id) retain {
    return self;
}

@end

Assicurati di leggere l'header dispatch / once.h per capire cosa sta succedendo. In questo caso i commenti dell'intestazione sono più applicabili dei documenti o della pagina man.

Ho messo singleton in una classe, quindi altre classi possono ereditare le proprietà singleton.

Singleton.h:

static id sharedInstance = nil;

#define DEFINE_SHARED_INSTANCE + (id) sharedInstance {  return [self sharedInstance:&sharedInstance]; } \
                               + (id) allocWithZone:(NSZone *)zone { return [self allocWithZone:zone forInstance:&sharedInstance]; }

@interface Singleton : NSObject {

}

+ (id) sharedInstance;
+ (id) sharedInstance:(id*)inst;

+ (id) allocWithZone:(NSZone *)zone forInstance:(id*)inst;

@end

Singleton.m:

#import "Singleton.h"


@implementation Singleton


+ (id) sharedInstance { 
    return [self sharedInstance:&sharedInstance];
}

+ (id) sharedInstance:(id*)inst {
    @synchronized(self)
    {
        if (*inst == nil)
            *inst = [[self alloc] init];
    }
    return *inst;
}

+ (id) allocWithZone:(NSZone *)zone forInstance:(id*)inst {
    @synchronized(self) {
        if (*inst == nil) {
            *inst = [super allocWithZone:zone];
            return *inst;  // assignment and return on first allocation
        }
    }
    return nil; // on subsequent allocation attempts return nil
}

- (id)copyWithZone:(NSZone *)zone {
    return self;
}

- (id)retain {
    return self;
}

- (unsigned)retainCount {
    return UINT_MAX;  // denotes an object that cannot be released
}

- (void)release {
    //do nothing
}

- (id)autorelease {
    return self;
}


@end

Ed ecco un esempio di qualche classe, che vuoi diventare singleton.

#import "Singleton.h"

@interface SomeClass : Singleton {

}

@end

@implementation SomeClass 

DEFINE_SHARED_INSTANCE;

@end

L'unica limitazione relativa alla classe Singleton è che si tratta della sottoclasse NSObject. Ma la maggior parte delle volte che uso i singleton nel mio codice sono in realtà sottoclassi di NSObject, quindi questa classe mi semplifica davvero la vita e rende il codice più pulito.

Funziona anche in un ambiente raccolto non spazzatura.

@interface MySingleton : NSObject {
}

+(MySingleton *)sharedManager;

@end


@implementation MySingleton

static MySingleton *sharedMySingleton = nil;

+(MySingleton*)sharedManager {
    @synchronized(self) {
        if (sharedMySingleton == nil) {
            [[self alloc] init]; // assignment not done here
        }
    }
    return sharedMySingleton;
}


+(id)allocWithZone:(NSZone *)zone {
    @synchronized(self) {
        if (sharedMySingleton == nil) {
            sharedMySingleton = [super allocWithZone:zone];
            return sharedMySingleton;  // assignment and return on first allocation
        }
    }
    return nil; //on subsequent allocation attempts return nil
}


-(void)dealloc {
    [super dealloc];
}

-(id)copyWithZone:(NSZone *)zone {
    return self;
}


-(id)retain {
    return self;
}


-(unsigned)retainCount {
    return UINT_MAX;  //denotes an object that cannot be release
}


-(void)release {
    //do nothing    
}


-(id)autorelease {
    return self;    
}


-(id)init {
    self = [super init];
    sharedMySingleton = self;

    //initialize here

    return self;
}

@end

Non dovrebbe essere sicuro per i thread ed evitare la costosa cura della prima chiamata?

+ (MySingleton*)sharedInstance
{
    if (sharedInstance == nil) {
        @synchronized(self) {
            if (sharedInstance == nil) {
                sharedInstance = [[MySingleton alloc] init];
            }
        }
    }
    return (MySingleton *)sharedInstance;
}

Ecco una macro che ho messo insieme:

http://github.com/cjhanson/Objective-C-Optimized-Singleton

Si basa su il lavoro qui di Matt Gallagher Ma cambiando l'implementazione per utilizzare il metodo che sfrigola come descritto qui da Dave MacLachlan di Google .

Accolgo con favore commenti / contributi.

Che ne dici

static MyClass *gInstance = NULL;

+ (MyClass *)instance
{
    if (gInstance == NULL) {
        @synchronized(self)
        {
            if (gInstance == NULL)
                gInstance = [[self alloc] init];
        }
    }

    return(gInstance);
}

Quindi eviti il ??costo della sincronizzazione dopo l'inizializzazione?

Per una discussione approfondita del modello singleton in Objective-C, guarda qui:

Uso del modello Singleton in Objective-C

  

KLSingleton è:

     
      
  1. Sottoclassabile (all'ennesima potenza)
  2.   
  3. ARC compatibile
  4.   
  5. Sicuro con alloc e init
  6.   
  7. Caricato pigramente
  8.   
  9. thread-safe
  10.   
  11. Senza blocco (utilizza + inizializza, non @sincronizza)
  12.   
  13. senza Macro
  14.   
  15. senza Swizzle
  16.   
  17. semplice
  18.   

KLSingleton

Non vuoi sincronizzarti su self ... Dato che l'oggetto self non esiste ancora! Si finisce per bloccare un valore ID temporaneo. Vuoi assicurarti che nessun altro possa eseguire metodi di classe (sharedInstance, alloc, allocWithZone :, ecc.), Quindi devi sincronizzare invece sull'oggetto class:

@implementation MYSingleton

static MYSingleton * sharedInstance = nil;

+( id )sharedInstance {
    @synchronized( [ MYSingleton class ] ) {
        if( sharedInstance == nil )
            sharedInstance = [ [ MYSingleton alloc ] init ];
    }

    return sharedInstance;
}

+( id )allocWithZone:( NSZone * )zone {
    @synchronized( [ MYSingleton class ] ) {
        if( sharedInstance == nil )
            sharedInstance = [ super allocWithZone:zone ];
    }

    return sharedInstance;
}

-( id )init {
    @synchronized( [ MYSingleton class ] ) {
        self = [ super init ];
        if( self != nil ) {
            // Insert initialization code here
        }

        return self;
    }
}

@end

Volevo solo lasciarlo qui, quindi non lo perdo. Il vantaggio di questo è che è utilizzabile in InterfaceBuilder, che è un enorme vantaggio. Questo è tratto da un'altra domanda che ho posto :

static Server *instance;

+ (Server *)instance { return instance; }

+ (id)hiddenAlloc
{
    return [super alloc];
}

+ (id)alloc
{
    return [[self instance] retain];
}


+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        instance = [[Server hiddenAlloc] init];
    }
}

- (id) init
{
    if (instance)
        return self;
    self = [super init];
    if (self != nil) {
        // whatever
    }
    return self;
}
static mySingleton *obj=nil;

@implementation mySingleton

-(id) init {
    if(obj != nil){     
        [self release];
        return obj;
    } else if(self = [super init]) {
        obj = self;
    }   
    return obj;
}

+(mySingleton*) getSharedInstance {
    @synchronized(self){
        if(obj == nil) {
            obj = [[mySingleton alloc] init];
        }
    }
    return obj;
}

- (id)retain {
    return self;
}

- (id)copy {
    return self;
}

- (unsigned)retainCount {
    return UINT_MAX;  // denotes an object that cannot be released
}

- (void)release {
    if(obj != self){
        [super release];
    }
    //do nothing
}

- (id)autorelease {
    return self;
}

-(void) dealloc {
    [super dealloc];
}
@end

So che ci sono molti commenti su questa "domanda", ma non vedo molte persone che suggeriscono di usare una macro per definire il singleton. È un modello così comune e una macro semplifica notevolmente il singleton.

Ecco le macro che ho scritto sulla base di diverse implementazioni di Objc che ho visto.

Singeton.h

/**
 @abstract  Helps define the interface of a singleton.
 @param  TYPE  The type of this singleton.
 @param  NAME  The name of the singleton accessor.  Must match the name used in the implementation.
 @discussion
 Typcially the NAME is something like 'sharedThing' where 'Thing' is the prefix-removed type name of the class.
 */
#define SingletonInterface(TYPE, NAME) \
+ (TYPE *)NAME;


/**
 @abstract  Helps define the implementation of a singleton.
 @param  TYPE  The type of this singleton.
 @param  NAME  The name of the singleton accessor.  Must match the name used in the interface.
 @discussion
 Typcially the NAME is something like 'sharedThing' where 'Thing' is the prefix-removed type name of the class.
 */
#define SingletonImplementation(TYPE, NAME) \
static TYPE *__ ## NAME; \
\
\
+ (void)initialize \
{ \
    static BOOL initialized = NO; \
    if(!initialized) \
    { \
        initialized = YES; \
        __ ## NAME = [[TYPE alloc] init]; \
    } \
} \
\
\
+ (TYPE *)NAME \
{ \
    return __ ## NAME; \
}

Esempio di utilizzo:

MyManager.h

@interface MyManager

SingletonInterface(MyManager, sharedManager);

// ...

@end

MyManager.m

@implementation MyManager

- (id)init
{
    self = [super init];
    if (self) {
        // Initialization code here.
    }

    return self;
}

SingletonImplementation(MyManager, sharedManager);

// ...

@end

Perché una macro di interfaccia quando è quasi vuota? Coerenza del codice tra intestazione e file di codice; manutenibilità nel caso in cui si desideri aggiungere metodi più automatici o modificarlo in giro.

Sto usando il metodo di inizializzazione per creare il singleton come viene usato nella risposta più popolare qui (al momento della scrittura).

Con i metodi della classe Objective C, possiamo semplicemente evitare di usare il modello singleton nel solito modo, da:

[[Librarian sharedInstance] openLibrary]

a:

[Librarian openLibrary]

avvolgendo la classe all'interno di un'altra classe che ha solo Metodi di classe , in questo modo non è possibile creare accidentalmente istanze duplicate, poiché non stiamo creando alcuna istanza!

Ho scritto un blog più dettagliato qui :)

Per estendere l'esempio da @ robbie-hanson ...

static MySingleton* sharedSingleton = nil;

+ (void)initialize {
    static BOOL initialized = NO;
    if (!initialized) {
        initialized = YES;
        sharedSingleton = [[self alloc] init];
    }
}

- (id)init {
    self = [super init];
    if (self) {
        // Member initialization here.
    }
    return self;
}

La mia strada è semplice come questa:

static id instanceOfXXX = nil;

+ (id) sharedXXX
{
    static volatile BOOL initialized = NO;

    if (!initialized)
    {
        @synchronized([XXX class])
        {
            if (!initialized)
            {
                instanceOfXXX = [[XXX alloc] init];
                initialized = YES;
            }
        }
    }

    return instanceOfXXX;
}

Se il singleton è già inizializzato, il blocco LOCK non verrà inserito. Il secondo controllo se (! Inizializzato) è assicurarsi che non sia ancora inizializzato quando il thread corrente acquisisce il BLOCCO.

Non ho letto tutte le soluzioni, quindi perdona se questo codice è ridondante.

Questa è l'implementazione più sicura per i thread secondo me.

+(SingletonObject *) sharedManager
{
    static SingletonObject * sharedResourcesObj = nil;

    @synchronized(self)
    {
        if (!sharedResourcesObj)
        {
            sharedResourcesObj = [[SingletonObject alloc] init];
        }
    }

    return sharedResourcesObj;
}

Di solito uso un codice approssimativamente simile a quello nella risposta di Ben Hoffstein (che ho anche ottenuto da Wikipedia). Lo uso per i motivi indicati da Chris Hanson nel suo commento.

Tuttavia, a volte ho bisogno di inserire un singleton in un NIB, e in quel caso uso quanto segue:

@implementation Singleton

static Singleton *singleton = nil;

- (id)init {
    static BOOL initialized = NO;
    if (!initialized) {
        self = [super init];
        singleton = self;
        initialized = YES;
    }
    return self;
}

+ (id)allocWithZone:(NSZone*)zone {
    @synchronized (self) {
        if (!singleton)
            singleton = [super allocWithZone:zone];     
    }
    return singleton;
}

+ (Singleton*)sharedSingleton {
    if (!singleton)
        [[Singleton alloc] init];
    return singleton;
}

@end

Lascio l'implementazione di -retain (ecc.) al lettore, sebbene il codice sopra sia tutto ciò di cui hai bisogno in un ambiente garbage collection.

La risposta accettata, sebbene compilata, non è corretta.

+ (MySingleton*)sharedInstance
{
    @synchronized(self)  <-------- self does not exist at class scope
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}

Per documentazione Apple:

... Puoi adottare un approccio simile per sincronizzare i metodi di classe della classe associata, usando l'oggetto Class invece di self.

Anche se l'uso di sé funziona, non dovrebbe e questo mi sembra un errore di copia e incolla. L'implementazione corretta per un metodo factory di classe sarebbe:

+ (MySingleton*)getInstance
{
    @synchronized([MySingleton class]) 
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top