wake-up-neo.net

Come superare il mio obiettivo Objective-C?

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?

334
schwa

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

Il runtime invia initialize a ogni classe in un programma esattamente una volta appena prima che la classe, o qualsiasi classe che ne erediti, sia inviata il suo primo messaggio all'interno del programma. Pertanto, il metodo non può mai essere invocato se la classe non viene utilizzata. Il runtime invia il messaggio initialize alle classi in modo thread-safe. Le superclassi 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];
    }
}
207
Robbie Hanson
@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]

95
Ben Hoffstein

Secondo la mia altra risposta qui sotto, penso che dovresti fare:

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

Dato che Kendall ha pubblicato un singleton thread-safe che tenta di evitare i costi di blocco, ho pensato di lanciarne uno anche uno:

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

Va bene, lascia che ti spieghi come funziona:

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

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

  3. Caso conteso: se due thread tentano entrambi di chiamare sharedInstance allo stesso tempo EsharedInstance non esiste contemporaneamente, entrambi inizializzeranno nuove istanze del singleton e provare a CAS in posizione. Chiunque vinca il CAS ritorna immediatamente, qualunque cosa perda rilascia l'istanza appena assegnata e restituisce il (sharedInstance (ora impostato )__. Il singolo OSAtomicCompareAndSwapPtrBarrier funge sia da barriera di scrittura per il thread di impostazione sia da barriera di lettura dal thread di test.

58
Louis Gerbarg
 statico MyClass * sharedInst = nil; 
 
 + (id) sharedInstance 
 {
 @synchronize (self) {
 se (sharedInst == nil) {
/* sharedInst set up in init */
 [[auto allocazione] init]; 
} 
} 
 return sharedInst; 
} 
 
 - (id) init 
 {
 if (sharedInst! = nil) {
 [ NSException raise: NSInternalInconsistencyException Formato 
: @ "[% @% @] Non può essere chiamato; usa + [% @% @] invece"], 
 NSStringFromClass ([auto classe]), NSStringFromSelector ( _cmd), 
 NSStringFromClass ([self class]), 
 NSStringFromSelector (@selector (sharedInstance) "]; 
} else if (self = [super init]) {
 sharedInst = self; 
/* Qualunque classe specifica qui */
} 
 return sharedInst; 
} 
 
/* Questi probabilmente non fanno nulla in 
 Un'app GC singleton 
 come un singleton effettivo in un'applicazione 
 non CG 
 */
 - (NSUInteger) retainCount 
 {
 return NSUIntegerMax ; 
} 
 
 - (svincolo oneway) release 
 {
} 
 
 - (id) conserva 
 {
 return sharedInst; 
} 
 
 - (id) autorelease 
 {
 return sharedInst; 
} 
14

Modifica: questa implementazione è stata obsoleta con ARC. Si prega di dare un'occhiata a Come implementare un singleton Objective-C compatibile con ARC? per la 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 controllare 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 attraverso 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 che si tradurrà in un secondo trigger di +initialize.

Potresti pensare di dover verificare implicitamente l'inizializzazione duplicata nel blocco init in quanto tale:

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

Ma ti sparerai ai piedi; 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 asserzione o solo con NSAssert.)

12
lorean

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

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

10
Matthieu Cormier

Ho una variante interessante su sharedInstance che è thread-safe, ma non si blocca dopo l'inizializzazione. Non ne sono ancora abbastanza sicuro per modificare la risposta principale 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'intestazione dispatch/once.h per capire cosa sta succedendo. In questo caso i commenti di intestazione sono più applicabili rispetto alla pagina man o doc.

6
quellish

Ho eseguito il rollup singleton in una classe, in modo che altre classi possano 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 della classe Singleton è che è una sottoclasse NSObject. Ma la maggior parte del tempo che uso i singleton nel mio codice sono in effetti sottoclassi NSObject, quindi questa classe mi facilita la vita e rende il codice più pulito.

5
obscenum

Non dovrebbe questo essere thread sicuro ed evitare il costoso blocco dopo la prima chiamata?

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

Funziona anche in un ambiente non-garbage collection.

@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
2
lajos

Che ne dite di

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

Quindi si evita il costo di sincronizzazione dopo l'inizializzazione?

2
Tony

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

Uso del pattern Singleton in Objective-C

2
Fred McCann

Ecco una macro che ho messo insieme:

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

È basato su il lavoro qui di Matt Gallagher Ma cambiando l'implementazione da usare metodo swizzling come descritto qui da Dave MacLachlan di Google .

Accolgo con favore commenti/contributi.

2
CJ Hanson

KLSingleton è:

  1. Subclassible (all'n-th degree)
  2. ARC compatibile
  3. Sicuro con alloc e init
  4. Caricato pigramente
  5. Thread-safe
  6. Lock-free (usa + inizializza, non @synchronize)
  7. Macro-gratis
  8. Swizzle-gratis
  9. Semplice

KLSingleton

1
kevinlawler

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 enormemente il singleton.

Ecco i macro che ho scritto in base a diverse implementazioni 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 dell'interfaccia quando è quasi vuota? Coerenza del codice tra l'intestazione e i file di codice; manutenibilità nel caso in cui si desideri aggiungere più metodi automatici o modificarli.

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

0
Nate

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

[[Librarian sharedInstance] openLibrary]

a:

[Librarian openLibrary]

avvolgendo la classe all'interno di un'altra classe che ha appena Metodi di classe , in questo modo non vi è alcuna possibilità di creare accidentalmente istanze duplicate, dato che non stiamo creando alcuna istanza!

Ho scritto un blog più dettagliato qui :)

0
chunkyguy

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

Non vuoi sincronizzare su se stessi ... Poiché l'oggetto stesso non esiste ancora! Finisci per bloccare un valore ID temporaneo. Si vuole assicurare che nessun altro possa eseguire i metodi di classe (sharedInstance, alloc, allocWithZone :, etc), quindi è necessario sincronizzarsi sull'oggetto di classe:

@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
0
Rob Dotson

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) serve per assicurarsi che non sia ancora inizializzato quando il thread corrente acquisisce il LOCK.

0
TienDC
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
0
user370199

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

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;
}
0
Dan Rosenstark

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

Questa è l'implementazione più filiforme secondo me.

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

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

    return sharedResourcesObj;
}
0
Zolt