Perché Apple consiglia di utilizzare dispatch_once per implementare il modello singleton in ARC?


305

Qual è il motivo esatto per utilizzare dispatch_once nell'accessorio di istanza condiviso di un singleton in ARC?

+ (MyClass *)sharedInstance
{
    //  Static local predicate must be initialized to 0
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken = 0;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyClass alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}

Non è una cattiva idea istanziare il singleton in modo asincrono in background? Voglio dire cosa succede se richiedo quell'istanza condivisa e mi affido immediatamente a essa, ma dispatch_once impiega fino a Natale per creare il mio oggetto? Non ritorna immediatamente giusto? Almeno questo sembra essere il punto centrale di Grand Central Dispatch.

Allora perché lo stanno facendo?


Note: static and global variables default to zero.
ikkentim,

Risposte:


418

dispatch_once()è assolutamente sincrono. Non tutti i metodi GCD fanno le cose in modo asincrono (caso per esempio, dispatch_sync()è sincrono). L'uso di dispatch_once()sostituisce il seguente linguaggio:

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

Il vantaggio di dispatch_once()questo è che è più veloce. È anche semanticamente più pulito, perché ti protegge anche da più thread facendo alloc init del tuo commonInstance - se provano tutti allo stesso tempo esatto. Non consentirà la creazione di due istanze. L'intera idea di dispatch_once()"eseguire qualcosa una volta e una sola volta", che è esattamente ciò che stiamo facendo.


4
Per motivi di argomento, devo notare che la documentazione non dice che viene eseguita in modo sincrono. Dice solo che più invocazioni simultanee verranno serializzate.
esperto

5
Sostieni che è più veloce - quanto più veloce? Non ho motivo di pensare che non stai dicendo la verità, ma mi piacerebbe vedere un semplice punto di riferimento.
Joshua Gross

29
Ho appena fatto un semplice benchmark (su iPhone 5) e sembra che dispatch_once sia circa 2 volte più veloce di @sincronizzato.
Joshua Gross

3
@ReneDohan: se sei sicuro al 100% che nessuno chiama mai quel metodo da un thread diverso, allora funziona. Ma l'utilizzo dispatch_once()è davvero semplice (soprattutto perché Xcode lo completerà anche automaticamente in uno snippet di codice completo per te) e significa che non devi nemmeno considerare se il metodo deve essere thread-safe.
Lily Ballard,

1
@siuying: in realtà, non è vero. Innanzitutto, tutto ciò che viene fatto +initializeaccade prima che la classe venga toccata, anche se non stai ancora cercando di creare la tua istanza condivisa. In generale, l'inizializzazione lazy (creando qualcosa solo quando necessario) è migliore. In secondo luogo, anche la tua richiesta di prestazione non è vera. dispatch_once()richiede quasi esattamente la stessa quantità di tempo di dire if (self == [MyClass class])in +initialize. Se hai già un +initialize, quindi sì creando l'istanza condivisa c'è più veloce, ma la maggior parte delle classi no.
Lily Ballard

41

Perché funzionerà solo una volta. Quindi se provi ad accedervi due volte da thread diversi non causerà problemi.

Mike Ash ha una descrizione completa nel suo post sul blog Care and Feeding of Singletons .

Non tutti i blocchi GCD vengono eseguiti in modo asincrono.


Lily è una risposta migliore, ma lascio la mia per mantenere il link al post di Mike Ash.
Abizern
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.