32

Quale metodo di sincronizzazione utilizzare per garantire un singleton rimane un singleton?Creazione istantanea di thread di un singleton

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

o utilizzare un mutex?

#import <pthread.h> 

static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER; 

+(Foo*)sharedInstance 
{ 
    pthread_mutex_lock(&_mutex); 
    if (nil == _sharedInstance) 
    { 
     _sharedInstance = [[Foo alloc] init]; 
     ... 
    } 
    pthread_mutex_unlock(&_mutex); 
    return _sharedInstance; 
} 

Hmmm .. ci sono commenti su questo?

+2

potreste essere interessati a leggere questo (http: //steve.yegge. googlepages.com/singleton-considered-stupid). – sand

+1

Nonostante l'odio di yegge per i singleton, hanno sicuramente uno scopo su iPhone. Ma se stai semplicemente creando uno 'spazio dei nomi', usa invece i metodi di classe. – bentford

+0

@bentford - Ho programmato iPhone da 4 anni, vecchio codice e nuovo. Ho visto singleton usati una volta (in un codice piuttosto scadente). –

risposta

55

Assicurarsi di leggere la discussione su questo domanda/risposta, anche. Why should we separate alloc and init calls to avoid deadlocks in Objective-C?


Espansione sul problema della condizione di gara; la correzione reale non deve avere l'inizializzazione indeterminata all'interno dell'applicazione. indeterminati o pigri risultati di inizializzazione nel comportamento che può facilmente cambiare a causa di cambiamenti apparentemente innocue - configurazione, modifiche al codice "estranei", ecc ...

Meglio per inizializzare in modo esplicito sottosistemi su un noto buon punto in la durata della vita del programma. Cioè rilasciare [MyClass sharedInstance]; nel metodo applicationDidFinishLaunching: del delegato dell'App se si in realtà è necessario che il sottosistema sia inizializzato all'inizio del programma (o lo si sposta anche prima, se si desidera essere in più difensivo).

Meglio ancora per spostare completamente l'inizializzazione su quel metodo. Cioè [MyClass initializeSharedInstance]; dove +sharedInstance asserisce() se tale metodo non viene chiamato per primo.

Per quanto io sia un fan della convenienza, 25 anni di programmazione ObjC mi hanno insegnato che l'inizializzazione pigra è fonte di più mal di testa per la manutenzione e il refactoring di quanto valga.


Mentre la condizione di competizione esiste descritto di seguito, questo codice non risolve ciò che è descritto qui di seguito. Lo ha fatto per un paio di decenni quando non ci siamo preoccupati della concorrenza negli inizializzatori di istanze condivise. Lasciando il codice sbagliato per la prosperità.

Ricorda che per le risposte altrimenti corrette di Colin e Harald, c'è una condizione di gara molto sottile che potrebbe portarti ad un mondo di guai.

Vale a dire, se il -init della classe allocata si verifica per chiamare il metodo sharedInstance, lo farà prima che la variabile sia impostata. In entrambi i casi porterà a un punto morto.

Questa è l'unica volta in cui si desidera separare l'alloc e l'init. Cribbing codice di Colin, perché è la soluzione migliore (supponendo che Mac OS X):

+(MyClass *)sharedInstance 
{ 
    static MyClass *sharedInstance = nil; 
    static dispatch_once_t pred; 

    // partial fix for the "new" concurrency issue 
    if (sharedInstance) return sharedInstance; 
    // partial because it means that +sharedInstance *may* return an un-initialized instance 
    // this is from https://stackoverflow.com/questions/20895214/why-should-we-separate-alloc-and-init-calls-to-avoid-deadlocks-in-objective-c/20895427#20895427 

    dispatch_once(&pred, ^{ 
     sharedInstance = [MyClass alloc]; 
     sharedInstance = [sharedInstance init]; 
    }); 

    return sharedInstance; 
} 

nota questo funziona solo su Mac OS X; X 10.6+ e iOS 4.0+, in particolare.Nei sistemi operativi precedenti, in cui i blocchi non sono disponibili, utilizzare una serratura o uno dei vari modi per fare qualcosa una volta che non è basato sui blocchi.


Il motivo sopra riportato non impedisce il problema descritto nel testo e causa un deadlock quando viene rilevato. Il problema è che il dispatch_once() non è rientranti e, quindi, se lo init chiama sharedInstance, città di cuneo.

+0

Grazie per questo, anche se mi ci è voluta mezz'ora per trovare il riferimento corretto alla sintassi dei blocchi (^) http: // developer. apple.com/Mac/library/documentation/Cocoa/Conceptual/Blocks/Blocks.pdf, che per qualche motivo non è incluso nella documentazione dell'obiettivo C –

+0

Grazie. Era anche la prima cosa a cui pensavo mentre vedevo la tua risposta. Non sapevo che esistesse la possibilità di scrivere tali blocchi nell'obiettivo c (come l'implementazione anonima della classe in Java o i delegati in C#). Questa è la risposta per il mio altro problema qui: http://stackoverflow.com/questions/2118728/method-signature-for-a-selector Invece di usare i callback ora utilizzo i blocchi. :) Forse, inizializzo il mio singleton in AppDelegate e in realtà non mi interessa la concorrenza di thread. L'istanza è semplice lì, quando necessario. È un tipo di cache per poche cose come le immagini ecc. – MacTouch

+4

@bbum, (1) perché dovrebbe '-init' chiama' + sharedInstance', e (2) perché includi la riga 'if (sharedInstance) return sharedInstance; '? – ma11hew28

2

Questo CocoaDev page può essere utile per le vostre esigenze.

+0

Grazie. Hmm .. Non ho pensato alla possibilità di mantenere o di esagerare con un singleton. Diavolo, chi fa queste cose? :) – MacTouch

38

Il filo modo più veloce sicuro per farlo è con Grand Central Dispatch (libdispatch) e dispatch_once()

+(MyClass *)sharedInstance 
{ 
    static MyClass *sharedInstance = nil; 
    static dispatch_once_t pred; 

    dispatch_once(&pred, ^{ 
     sharedInstance = [[MyClass alloc] init]; 
    }); 

    return sharedInstance; 
} 
+0

perché devi usare dispatch_once? Perché non puoi semplicemente fare un'istruzione if in questo modo: "if (sharedInstance == nil) { sharedInstance = [MyClass alloc] init];" – Honey

+1

questo non garantisce che verrà assegnato solo una volta. l'invio una volta garantisce che il blocco verrà eseguito una sola volta anche se più thread chiamano lo stesso codice contemporaneamente. Nel tuo codice un thread può eseguire l'istruzione if, quindi andare ad avviare l'allocazione dell'istanza, quindi un altro thread allo stesso tempo valuta l'istruzione if e quindi assegna una seconda istanza e ora boom hai memoria trapelata che non puoi recuperare . Il tuo codice è funzionale, ma non gestirà casi come quello che ho appena descritto. –

+0

Grazie, ho capito che si tratta di thread-safe. Quello che non capisco è perché non basta usare dispatch_once abbastanza? Perché continuo a vedere le persone fare se istruzione + dispatch_once + static + metodo di classe? Se stai utilizzando dispatch_once, perché stai specificando statico? – Honey

12

se qualcuno avesse voglia, qui è una macro per la stessa cosa:

/*! 
    * @function Singleton GCD Macro 
    */ 
    #ifndef SINGLETON_GCD 
    #define SINGLETON_GCD(classname)       \ 
                   \ 
    + (classname *)shared##classname {       \ 
                   \ 
     static dispatch_once_t pred;       \ 
     static classname * shared##classname = nil;    \ 
     dispatch_once(&pred, ^{        \ 
      shared##classname = [[self alloc] init];   \ 
     });              \ 
     return shared##classname;        \ 
    }               
    #endif 
1

se qualcuno avesse voglia, qui è un'altra macro per la stessa cosa :)

IMHO, offre una maggiore flessibilità rispetto al the other variations.

#define SHARED_INSTANCE(...) ({\ 
    static dispatch_once_t pred;\ 
    static id sharedObject;\ 
    dispatch_once(&pred, ^{\ 
     sharedObject = (__VA_ARGS__);\ 
    });\ 
    sharedObject;\ 
}) 

utilizzo, di una riga di inizializzazione:

+ (instancetype) sharedInstanceOneLine { 
    return SHARED_INSTANCE([[self alloc] init]); 
} 

utilizzo, inizializzazione multi-linea (Avviso parentesi graffe intorno al blocco di codice):

+ (instancetype) sharedInstanceMultiLine { 
    return SHARED_INSTANCE({ 
     NSLog(@"creating shared instance"); 
     CGFloat someValue = 84/2.0f; 
     [[self alloc] initWithSomeValue:someValue]; // no return statement 
    }); 
} 

utilizzo nella parte destra di un incarico:

- (void) someMethod { 
    MethodPrivateHelper *helper = SHARED_INSTANCE([[MethodPrivateHelper alloc] init]); 
    // do smth with the helper 
} 
// someMethod should not call itself to avoid deadlock, see bbum's answer 

Questa modifica utilizza due funzionalità linguistiche: l'estensione GCC compound expressions, supportata anche da Clang e C99 variadic macros support.

Dopo la pre-elaborazione, l'output sarà simile (è possibile verificare voi stessi invocando Product > Perform Action > Preprocess "YourClassName.m" in Xcode 5):

+ (instancetype) sharedInstanceOneLine { 
    return ({ 
     static dispatch_once_t pred; 
     static id sharedObject; 
     dispatch_once(&pred, ^{ 
      sharedObject = ([[self alloc] init]); 
     }); 
     sharedObject; // this object will be returned from the block 
    }); 
} 

+ (instancetype) sharedInstanceMultiLine { 
    return ({ 
     static dispatch_once_t pred; 
     static id sharedObject; 
     dispatch_once(&pred, ^{ 
      sharedObject = ({ 
       NSLog(@"creating shared instance"); 
       CGFloat someValue = 84/2.0f; 
       [[self alloc] initWithSomeValue:someValue]; 
      }); 
     }); 
     sharedObject; 
    }); 
} 

- (void) someMethod { 
    MethodPrivateHelper *helper = ({ 
     static dispatch_once_t pred; 
     static id sharedObject; 
     dispatch_once(&pred, ^{ 
      sharedObject = ([[MethodPrivateHelper alloc] init]); 
     }); 
     sharedObject; 
    }); 
} 
Problemi correlati