Pourquoi Apple recommande-t-il d'utiliser dispatch_once pour implémenter le modèle singleton sous ARC?

305

Quelle est la raison exacte de l'utilisation de dispatch_once dans l'accesseur d'instance partagée d'un singleton sous 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;
}

N'est-ce pas une mauvaise idée d'instancier le singleton de manière asynchrone en arrière-plan? Je veux dire que se passe-t-il si je demande cette instance partagée et que j'y compte immédiatement, mais dispatch_once prend jusqu'à Noël pour créer mon objet? Il ne revient pas immédiatement non? Au moins, cela semble être tout l'intérêt de Grand Central Dispatch.

Alors pourquoi font-ils ça?

Membre fier
la source
Note: static and global variables default to zero.
ikkentim

Réponses:

418

dispatch_once()est absolument synchrone. Toutes les méthodes GCD ne font pas les choses de manière asynchrone (dans le cas présent, elles dispatch_sync()sont synchrones). L'utilisation de dispatch_once()remplace l'idiome suivant:

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

L'avantage de dispatch_once()cela est que c'est plus rapide. Il est également plus propre sur le plan sémantique, car il vous protège également de plusieurs threads faisant l'allocation init de votre instance partagée - s'ils essaient tous en même temps. Il ne permettra pas la création de deux instances. L'idée de dispatch_once()"faire quelque chose une fois et une seule fois", c'est précisément ce que nous faisons.

Lily Ballard
la source
4
Pour les besoins de l'argument, je dois noter que la documentation ne dit pas qu'elle est exécutée de manière synchrone. Il indique seulement que plusieurs appels simultanés seront sérialisés.
expert
5
Vous prétendez que c'est plus rapide - combien plus rapide? Je n'ai aucune raison de penser que vous ne dites pas la vérité, mais j'aimerais voir une référence simple.
Joshua Gross,
29
Je viens de faire un simple benchmark (sur iPhone 5) et il semble que dispatch_once soit environ 2x plus rapide que @synchronized.
Joshua Gross
3
@ReneDohan: Si vous êtes sûr à 100% que personne n'appelle jamais cette méthode à partir d'un thread différent, alors cela fonctionne. Mais l'utilisation dispatch_once()est vraiment simple (en particulier parce que Xcode va même le compléter automatiquement dans un extrait de code complet pour vous) et signifie que vous n'avez même jamais à vous demander si la méthode doit être thread-safe.
Lily Ballard
1
@siuying: En fait, ce n'est pas vrai. Tout d'abord, tout ce qui est fait +initializese produit avant que la classe ne soit touchée, même si vous n'essayez pas encore de créer votre instance partagée. En général, l'initialisation paresseuse (créer quelque chose uniquement en cas de besoin) est préférable. Deuxièmement, même votre revendication de performance n'est pas vraie. dispatch_once()prend presque exactement la même quantité de temps que dire if (self == [MyClass class])dans +initialize. Si vous en avez déjà un +initialize, alors oui, la création de l'instance partagée est plus rapide, mais la plupart des classes n'en ont pas.
Lily Ballard
41

Parce qu'il ne fonctionnera qu'une seule fois. Donc, si vous essayez d'y accéder deux fois à partir de différents threads, cela ne posera pas de problème.

Mike Ash a une description complète dans son article de blog Care and Feeding of Singletons .

Tous les blocs GCD ne sont pas exécutés de manière asynchrone.

Abizern
la source
Lily's est une meilleure réponse, mais je laisse la mienne pour garder le lien vers le post de Mike Ash.
Abizern