Warum empfiehlt Apple, dispatch_once für die Implementierung des Singleton-Musters unter ARC zu verwenden?

305

Was ist der genaue Grund für die Verwendung von dispatch_once im gemeinsam genutzten Instanz-Accessor eines Singletons unter 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;
}

Ist es nicht eine schlechte Idee, den Singleton asynchron im Hintergrund zu instanziieren? Ich meine, was passiert, wenn ich diese gemeinsam genutzte Instanz anfordere und mich sofort darauf verlasse, aber dispatch_once bis Weihnachten braucht, um mein Objekt zu erstellen? Es kehrt nicht sofort zurück, oder? Zumindest scheint das der springende Punkt bei Grand Central Dispatch zu sein.

Warum machen sie das?

Stolzes Mitglied
quelle
Note: static and global variables default to zero.
Ikkentim

Antworten:

418

dispatch_once()ist absolut synchron. Nicht alle GCD-Methoden führen asynchrone Aktionen aus (Beispiel: dispatch_sync()Synchron). Die Verwendung von dispatch_once()ersetzt die folgende Redewendung:

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

Der Vorteil dispatch_once()davon ist, dass es schneller ist. Es ist auch semantisch sauberer, da es Sie auch vor mehreren Threads schützt, die die Initiierung Ihrer sharedInstance durchführen - wenn alle es genau zur gleichen Zeit versuchen. Es können keine zwei Instanzen erstellt werden. Die gesamte Idee von dispatch_once()"etwas einmal und nur einmal ausführen" ist genau das, was wir tun.

Lily Ballard
quelle
4
Für das Argument muss ich beachten, dass die Dokumentation nicht besagt, dass sie synchron ausgeführt wird. Es heißt nur, dass mehrere gleichzeitige Aufrufe serialisiert werden.
Experte
5
Sie behaupten, dass es schneller ist - wie viel schneller? Ich habe keinen Grund zu der Annahme, dass Sie nicht die Wahrheit sagen, aber ich würde gerne einen einfachen Benchmark sehen.
Joshua Gross
29
Ich habe gerade einen einfachen Benchmark (auf dem iPhone 5) durchgeführt und es sieht so aus, als ob dispatch_once ungefähr 2x schneller ist als @synchronized.
Joshua Gross
3
@ReneDohan: Wenn Sie zu 100% sicher sind, dass niemand diese Methode jemals von einem anderen Thread aus aufruft, funktioniert sie. Die Verwendung dispatch_once()ist jedoch sehr einfach (insbesondere, weil Xcode es sogar automatisch zu einem vollständigen Code-Snippet für Sie vervollständigt) und bedeutet, dass Sie nicht einmal überlegen müssen, ob die Methode threadsicher sein muss.
Lily Ballard
1
@siuying: Eigentlich stimmt das nicht. Zunächst geschieht alles, was in ausgeführt wird +initialize, bevor die Klasse berührt wird, auch wenn Sie noch nicht versuchen, Ihre freigegebene Instanz zu erstellen. Im Allgemeinen ist eine verzögerte Initialisierung (nur bei Bedarf etwas erstellen) besser. Zweitens ist selbst Ihr Leistungsanspruch nicht wahr. dispatch_once()dauert fast genauso lange wie if (self == [MyClass class])in +initialize. Wenn Sie bereits eine haben +initialize, ist das Erstellen der freigegebenen Instanz dort zwar schneller, die meisten Klassen jedoch nicht.
Lily Ballard
41

Weil es nur einmal läuft. Wenn Sie also versuchen, zweimal von verschiedenen Threads darauf zuzugreifen, wird dies kein Problem verursachen.

Mike Ash hat eine vollständige Beschreibung in seinem Blog-Beitrag Care and Feeding of Singletons .

Nicht alle GCD-Blöcke werden asynchron ausgeführt.

Abizern
quelle
Lily's ist eine bessere Antwort, aber ich verlasse meine, um den Link zu Mike Ashs Post zu behalten.
Abizern