Come dovrebbe essere il mio singleton Objective-C? [chiuso]


334

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?


27
Quello che hai va bene, anche se potresti spostare la dichiarazione della variabile globale nel tuo metodo + istanza (l'unico posto che deve essere usato, a meno che tu non gli permetta di essere impostato anche) e usare un nome come + defaultMyClass o + sharedMyClass per il tuo metodo. + istanza non rivela l'intenzione.
Chris Hanson,

Poiché è improbabile che la "risposta" a questa domanda cambierà presto, sto mettendo un blocco storico sulla domanda. Due motivi 1) Un sacco di opinioni, voti e buon contenuto 2) Per evitare che tu possa aprire / chiudere. Era una grande domanda per i suoi tempi, ma domande di questo tipo non sono appropriate per Stack Overflow. Ora abbiamo Code Review per il controllo del codice di lavoro. Si prega di prendere tutte le discussioni di questa domanda a questa meta domanda .
George Stocker,

Risposte:


207

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

Il runtime invia initializea ogni classe in un programma esattamente una volta appena prima della classe, o qualsiasi classe che eredita da essa, viene inviato 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 initializemessaggio 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];
    }
}

7
Se il runtime lo chiamerà una volta sola, cosa fa BOOL? È una precauzione nel caso in cui qualcuno chiami esplicitamente questa funzione dal proprio codice?
Aftermathew,

5
Sì, è una precauzione poiché la funzione può anche essere chiamata direttamente.
Robbie Hanson,

33
Ciò è necessario anche perché potrebbero esserci delle sottoclassi. Se non sovrascrivono +initializel'implementazione delle superclasse verrà chiamata se la sottoclasse viene utilizzata per la prima volta.
Sven,

3
@Paul puoi sovrascrivere il releasemetodo e renderlo vuoto. :)

4
@aryaxt: dai documenti elencati, questo è già sicuro per i thread. Quindi, la chiamata è una volta per periodo di runtime. Sembrerebbe essere la soluzione corretta, sicura per i thread ed efficiente in modo ottimale.
lilbyrdie,

95
@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]


7
Questo è tutto ciò che dovresti usare di solito per i single. Tra le altre cose, mantenere le classi separatamente istanziabili li rende più facili da testare, perché è possibile testare istanze separate invece di avere un modo per ripristinare il loro stato.
Chris Hanson,

3
Stig Brautaset: No, non va bene tralasciare @synchronized in questo esempio. È lì per gestire la possibile condizione di competizione di due thread che eseguono questa funzione statica allo stesso tempo, entrambi superando il test "if (! SharedSingleton)" allo stesso tempo e risultando in due [allocazioni MySingleton]. .. Il @synchronized {scope block} forza quell'ipotetico secondo thread in attesa che il primo thread esca dal {scope scope} prima di poter procedere. Spero che aiuti! =)
MechEthan,

3
Cosa impedisce a qualcuno di creare ancora la propria istanza dell'oggetto? MySingleton *s = [[MySingelton alloc] init];
lindon fox,

1
@lindonfox Qual è la risposta alla tua domanda?
Raffi Khatchadourian,

1
@Raffi - scusa, penso di aver dimenticato di incollare la mia risposta. Comunque, ho preso il libro Pro Objective-C Design Patterns for iOSe spiega come si crea un singelton "rigoroso". Fondamentalmente poiché non è possibile rendere privati ​​i metodi di avvio, è necessario sovrascrivere i metodi allocazione e copia. Quindi se provi a fare qualcosa del genere [[MySingelton alloc] init]otterrai un errore di runtime (anche se sfortunatamente non un errore di compilazione). Non capisco come tutti i dettagli della creazione dell'oggetto, ma tu attui ciò + (id) allocWithZone:(NSZone *)zoneche viene chiamatosharedSingleton
lindon fox,

59

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

6
Non preoccuparti di tutto quello che stai facendo sopra. Rendi i tuoi singoli (si spera estremamente pochi) singolarmente separatamente-istanziabili e hai solo un metodo condiviso / predefinito. Quello che hai fatto è necessario solo se vuoi davvero, davvero, SOLO una singola istanza della tua classe. Che non lo fai, esp. per test unitari.
Chris Hanson,

Il fatto è che questo è il codice di esempio di Apple per "creare un singleton". Ma sì, hai assolutamente ragione.
Colin Barrett,

1
Il codice di esempio di Apple è corretto se si desidera un singleton "vero" (ovvero un oggetto che può essere istanziato una sola volta, mai) ma come dice Chris, questo è raramente ciò che si desidera o è necessario mentre un tipo di istanza condivisa impostabile è ciò che si di solito voglio.
Luke Redpath,

Ecco una macro per il metodo sopra: gist.github.com/1057420 . Questo è quello che uso.
Kobski,

1
A parte i test unitari, non c'è nulla da dire su questa soluzione, giusto? Ed è veloce e sicuro.
LearnCocos2D

58

Dato che Kendall ha pubblicato un singleton thread-safe 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 whileciclo non viene mai eseguito e la funzione ritorna dopo aver semplicemente testato l'esistenza della variabile;

  2. Caso lento: se sharedInstancenon esiste, 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 sharedInstancecontemporaneamente E sharedInstance non esiste contemporaneamente, entrambi inizializzeranno nuove istanze del singleton e tenteranno di posizionarlo in CAS. Qualunque vince immediatamente il CAS restituisce, qualunque cosa perde rilascia l'istanza che ha appena assegnato e restituisce il (ora impostato) sharedInstance. Il singolo OSAtomicCompareAndSwapPtrBarrierfunge sia da barriera di scrittura per il thread di impostazione sia da barriera di lettura dal thread di test.


18
Questo è un overkill completo per il massimo una volta che può accadere durante la vita di un'applicazione. Tuttavia, è perfettamente corretto e la tecnica di confronto e scambio è uno strumento utile da conoscere, quindi +1.
Steve Madsen,

Bella risposta - la famiglia OSAtomic è una buona cosa da sapere
Bill

1
@ Louis: risposta incredibile, davvero illuminante! Una domanda però: cosa dovrebbe initfare il mio metodo nel tuo approccio? Fare un'eccezione quando sharedInstanceviene inizializzato non è una buona idea, credo. Cosa fare quindi per impedire all'utente di chiamare initdirettamente molte volte?
matm

2
In genere non lo prevengo. Ci sono spesso validi motivi per consentire a ciò che è generalmente un singleton di moltiplicarsi come istanza, il maggior numero di beni comuni è per alcuni tipi di test unitari. Se volessi davvero imporre una singola istanza, probabilmente avrei il metodo init per verificare se il globale esisteva e, in caso affermativo, avrei rilasciato se stesso e restituito il globale.
Louis Gerbarg,

1
@Tony ha ritardato la risposta, ma OSAtomicCompareAndSwapPtrBarrier richiede una volatilità. Forse la parola chiave volatile è impedire al compilatore di ottimizzare il controllo? Vedi: stackoverflow.com/a/5334727/449161 e developer.apple.com/library/mac/#documentation/Darwin/Reference/…
Ben Flynn

14
static MyClass * sharedInst = nil;

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

- (id) init
{
    if (sharedInst! = nil) {
        [Rilancio di NSException: NSInternalInconsistencyException
            formato: @ "[% @% @] non può essere chiamato; utilizzare + [% @% @] invece"],
            NSStringFromClass ([self class]), NSStringFromSelector (_cmd), 
            NSStringFromClass ([self class]),
            NSStringFromSelector (@selector (sharedInstance) "];
    } else if (self = [super init]) {
        sharedInst = self;
        / * Qualunque sia la classe specifica qui * /
    }
    return sharedInst;
}

/ * Probabilmente non fanno nulla
   un'app GC. Mantiene singleton
   come un vero singleton in a
   app non CG
* /
- (NSUInteger) retainCount
{
    return NSUIntegerMax;
}

- Rilascio (vuoto unidirezionale)
{
}

- (id) conserva
{
    return sharedInst;
}

- (id) rilascio automatico
{
    return sharedInst;
}

3
Ho notato che clang si lamenta di una perdita se non si assegna il risultato [[self alloc] init]a sharedInst.
pix0r,

Sovvertire sovvert in questo modo è un approccio piuttosto brutto all'IMO. Non scherzare con init e / o l'effettiva creazione dell'oggetto. Se invece cerchi un punto di accesso controllato a un'istanza condivisa, mentre non esegui il singleton nell'oggetto, ti divertirai più tardi se scrivi test ecc. I singleton duri sono troppo abusati.
occolo

12

Modifica: questa implementazione è obsoleta con ARC. Dai un'occhiata a Come posso 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 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 con conseguente seconda attivazione di +initialize.

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 al piede.

- (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.)


1
Viverei semplicemente più semplice ed eviterei del tutto l'inizializzazione.
Tom Andersen,


9

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

1
+1 che è davvero intrigante. Potrei usare class_replaceMethodper trasformarmi sharedInstancein un clone di simpleSharedInstance. In questo modo non dovresti mai preoccuparti di ottenere @synchronizednuovamente un lucchetto.
Dave DeLong,

È lo stesso effetto, usare exchangeImplementations significa che dopo init quando chiami sharedInstance, stai davvero chiamando simpleSharedInstance. In realtà ho iniziato con ReplaceMethod, ma ho deciso che era meglio cambiare le implementazioni in modo che l'originale esistesse ancora se necessario ...
Kendall Helmstetter Gelner,

In ulteriori test, non sono riuscito a far funzionare il metodo di sostituzione - nelle chiamate ripetute, il codice chiamava ancora l'originale sharedInstance invece di simpleSharedInstance. Penso che potrebbe essere perché sono entrambi metodi a livello di classe ... La sostituzione che ho usato era: class_replaceMethod (self, origSel, method_getImplementation (newMethod), method_getTypeEncoding (newMethod)); e alcune sue variazioni. Posso verificare il codice che ho pubblicato funziona e viene chiamato simpleSharedInstance dopo il primo passaggio tramite sharedInstance.
Kendall Helmstetter Gelner,

Puoi creare una versione thread-safe che non paghi i costi di blocco dopo l'inizializzazione senza fare un mucchio di runtime mucking, ho pubblicato un'implementazione di seguito.
Louis Gerbarg,

1
+1 ottima idea. Adoro quelle cose che si possono fare con l'autonomia. Ma nella maggior parte dei casi si tratta probabilmente di un'ottimizzazione prematura. Se dovessi davvero sbarazzarmi del costo della sincronizzazione, probabilmente userò la versione senza blocco di Louis.
Sven,

6

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 dell'intestazione sono più applicabili dei documenti o della pagina man.


5

Ho raggruppato 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 NSObject, quindi questa classe mi semplifica davvero la vita e rende il codice più pulito.


Potresti voler usare qualche altro meccanismo di blocco perché @synchronizedè terribilmente lento e dovrebbe essere evitato.
DarkDust,

2

Funziona anche in un ambiente raccolto senza immondizia.

@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

Non dovrebbe essere sicuro per i thread 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
La tecnica di blocco a doppio controllo utilizzata qui è spesso un vero problema in alcuni ambienti (vedi aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf o Google it). Fino a prova contraria, suppongo che Objective-C non sia immune. Vedi anche wincent.com/a/knowledge-base/archives/2006/01/… .
Steve Madsen,


2

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 eviti il ​​costo della sincronizzazione dopo l'inizializzazione?


Vedi le discussioni sul doppio blocco controllato in altre risposte.
i_am_jorf


1

KLSingleton è:

  1. Sottoclassabile (all'ennesima potenza)
  2. ARC compatibile
  3. Sicuro con alloceinit
  4. Caricato pigramente
  5. Thread-safe
  6. Senza blocco (utilizza + inizializza, non @sincronizza)
  7. Macro-gratis
  8. senza Swizzle
  9. Semplice

KLSingleton


1
Sto usando il tuo NSSingleton per il mio progetto e sembra essere incompatibile con KVO. Il fatto è che KVO crea una sottoclasse per ogni oggetto KVO con il prefisso NSKVONotifying_ MyClass . E fa in modo che MyClass + inizializzi e -init i metodi vengano chiamati due volte.
Oleg Trakhman,

Ho provato questo sull'ultimo Xcode e non ho avuto problemi a registrarmi o ricevere eventi KVO. Puoi verificarlo con il seguente codice: gist.github.com/3065038 Come ho già detto su Twitter, i metodi + inizializzazione vengono chiamati una volta per NSSingleton e una volta per ogni sottoclasse. Questa è una proprietà di Objective-C.
Kevinevler

Se si aggiunge NSLog(@"initialize: %@", NSStringFromClass([self class]));al +initializemetodo è possibile verificare che le classi siano inizializzate una sola volta.
Kevinevler

NSLog (@ "initialize:% @", NSStringFromClass ([self class]));
Oleg Trakhman,

Potresti anche averlo compatibile con IB. Il mio è: stackoverflow.com/questions/4609609/...
Dan Rosenstark

0

Non vuoi sincronizzarti su se stesso ... Dal momento 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 :, etc), 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

1
Il resto dei metodi, metodi di accesso, metodi di mutatore, ecc. Dovrebbero sincronizzarsi su se stessi. Tutti i metodi e gli inizializzatori di classe (+) (e probabilmente -dealloc) devono sincronizzarsi sull'oggetto classe. È possibile evitare la sincronizzazione manuale se si utilizzano le proprietà Objective-C 2.0 anziché i metodi di accesso / mutatore. Tutti object.property e object.property = foo, vengono automaticamente sincronizzati con sé.
Rob Dotson,

3
Spiega perché pensi che l' selfoggetto non esista in un metodo di classe. Il runtime determina quale implementazione del metodo richiamare in base allo stesso valore esatto che fornisce selfper ogni metodo (classe o istanza).
dreamlax,

2
All'interno di un metodo di classe, self è l'oggetto class. Provalo tu stesso:#import <Foundation/Foundation.h> @interface Eggbert : NSObject + (BOOL) selfIsClassObject; @end @implementation Eggbert + (BOOL) selfIsClassObject { return self == [Eggbert class]; } @end int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSLog(@"%@", [Eggbert selfIsClassObject] ? @"YES" : @"NO"); [pool drain]; return 0; }
jscs,

0

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

0
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

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 altri metodi automatici o modificarlo.

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


0

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

[[Librarian sharedInstance] openLibrary]

per:

[Librarian openLibrary]

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

Ho scritto un blog più dettagliato qui :)


Il tuo link non funziona più.
i_am_jorf

0

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

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 è chiaro che la marcatura initializedcome volatileè sufficiente. Vedi aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf .
i_am_jorf

0

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

-4

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.


2
Il tuo codice non è thread-safe. Utilizza sincronizzato nel metodo alloc, ma non nel metodo init. Il controllo del bool inizializzato non è thread-safe.
Mecki,

-5

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

Secondo la 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'utilizzo di auto 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;
}

6
auto più certamente lo fa esistere è ambito di classe. Si riferisce alla classe anziché all'istanza della classe. Le classi sono (principalmente) oggetti di prima classe.
schwa,

Perché metti @synchroninzed ALL'INTERNO di un metodo?
user4951

1
Come già detto schwa, self è l'oggetto di classe all'interno di un metodo di classe. Vedi il mio commento per uno snippet che lo dimostra.
jscs,

selfesiste, ma usandolo come identificatore passato @synchronizedverrà sincronizzato l'accesso ai metodi dell'istanza. Come sottolinea @ user490696, ci sono casi (come i singleton) in cui è preferibile usare l'oggetto class. Dalla Guida alla programmazione di Obj-C:You can take a similar approach to synchronize the class methods of the associated class, using the class object instead of self. In the latter case, of course, only one thread at a time is allowed to execute a class method because there is only one class object that is shared by all callers.
domina l'
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.