Question Utilisation d'un modèle de dispatch_once singleton dans Swift


J'essaie d'élaborer un modèle singleton approprié pour l'utilisation dans Swift. Jusqu'à présent, j'ai pu obtenir un modèle non-thread-safe fonctionnant comme:

class var sharedInstance:TPScopeManager {
    get {
        struct Static {
            static var instance : TPScopeManager? = nil
        }

        if !Static.instance {
            Static.instance = TPScopeManager()
        }

        return Static.instance!
    }
}

Envelopper l'instance singleton dans la structure statique devrait permettre à une seule instance de ne pas entrer en collision avec des instances singleton sans schémas de nommage complexes, et cela devrait rendre les choses assez privées. Évidemment cependant, ce modèle n'est pas sûr pour les threads, j'ai donc essayé d'ajouter dispatch_once à l'ensemble:

class var sharedInstance:TPScopeManager {
    get {
        struct Static {
            static var instance : TPScopeManager? = nil
            static var token : dispatch_once_t = 0
        }

        dispatch_once(Static.token) { Static.instance = TPScopeManager() }

        return Static.instance!
    }
}

Mais je reçois une erreur de compilateur sur le dispatch_once ligne:

Impossible de convertir le type de l'expression 'Void' en type '()'

J'ai essayé plusieurs variantes de la syntaxe, mais elles semblent toutes avoir le même résultat:

dispatch_once(Static.token, { Static.instance = TPScopeManager() })

Quel est le bon usage de dispatch_once en utilisant Swift? J'ai d'abord pensé que le problème était avec le bloc en raison de la () dans le message d'erreur, mais plus je le regarde, plus je pense qu'il peut être question d'obtenir le dispatch_once_t correctement défini.


532
2018-06-03 20:41


origine


Réponses:


tl; dr: utilisez le constante de classe approche si vous utilisez Swift 1.2 ou supérieur et le structure imbriquée approche si vous avez besoin de soutenir les versions antérieures.

D'après mon expérience avec Swift, il existe trois approches pour implémenter le modèle Singleton qui supporte l'initialisation paresseuse et la sécurité des threads.

Constante de classe

class Singleton  {
   static let sharedInstance = Singleton()
}

Cette approche prend en charge l'initialisation paresseuse parce que Swift paresseux initialise les constantes de classe (et les variables), et est thread safe par la définition de let. C'est maintenant manière officiellement recommandée instancier un singleton.

Les constantes de classe ont été introduites dans Swift 1.2. Si vous devez prendre en charge une version antérieure de Swift, utilisez l'approche de structure imbriquée ci-dessous ou une constante globale.

Structure imbriquée

class Singleton {
    class var sharedInstance: Singleton {
        struct Static {
            static let instance: Singleton = Singleton()
        }
        return Static.instance
    }
}

Ici, nous utilisons la constante statique d'une structure imbriquée en tant que constante de classe. C'est une solution de contournement pour l'absence de constantes de classe statiques dans Swift 1.1 et versions antérieures, et fonctionne toujours comme une solution de contournement pour l'absence de constantes statiques et de variables dans les fonctions.

dispatch_once

L'approche traditionnelle de l'Objective-C a été transférée à Swift. Je suis à peu près certain qu'il n'y a pas d'avantage sur l'approche de la structure imbriquée, mais je la mets ici de toute façon, car je trouve les différences de syntaxe intéressantes.

class Singleton {
    class var sharedInstance: Singleton {
        struct Static {
            static var onceToken: dispatch_once_t = 0
            static var instance: Singleton? = nil
        }
        dispatch_once(&Static.onceToken) {
            Static.instance = Singleton()
        }
        return Static.instance!
    }
}

Regarde ça GitHub projet pour les tests unitaires.


668
2018-06-10 17:57



Puisque Apple a maintenant clarifié que les variables de structure statiques sont initialisées à la fois paresseuses et enveloppées dans dispatch_once (voir la note à la fin de la publication), je pense que ma solution finale sera:

class WithSingleton {
    class var sharedInstance :WithSingleton {
        struct Singleton {
            static let instance = WithSingleton()
        }

        return Singleton.instance
    }
}

Cela tire parti de l'initialisation automatique paresseuse et sans danger pour les threads des éléments structurels statiques, cache en toute sécurité l'implémentation réelle du consommateur, maintient tout compactement compartimenté pour la lisibilité et élimine une variable globale visible.

Apple a clarifié que l'initialisateur paresseux sont thread-safe, donc il n'y a pas besoin de dispatch_once ou des protections similaires

L'initialisateur paresseux d'une variable globale (également pour les membres statiques de structures et d'enums) est lancé la première fois que global est accédé, et est lancé en tant que dispatch_once pour s'assurer que l'initialisation est atomique. Cela permet une manière cool d'utiliser dispatch_once dans votre code: déclarez simplement une variable globale avec un initialiseur et marquez-la private.

De ici


169
2018-06-03 20:55



Pour Swift 1.2 et au-delà:

class Singleton  {
   static let sharedInstance = Singleton()
}

Avec une preuve d'exactitude (tout le mérite va ici), il y a peu ou pas de raison d'utiliser l'une des méthodes précédentes pour les singletons.

Mettre à jour: Ceci est maintenant le officiel façon de définir singletons comme décrit dans le documents officiels!

En ce qui concerne les préoccupations sur l'utilisation static contre class. static devrait être celui à utiliser même quand class les variables deviennent disponibles. Les singletons ne sont pas destinés à être sous-classés, car cela aboutirait à plusieurs instances du singleton de base. En utilisant static applique ceci d'une manière belle, Swifty.

Pour Swift 1.0 et 1.1:

Avec les changements récents dans Swift, principalement de nouvelles méthodes de contrôle d'accès, je suis maintenant penché vers la façon plus propre d'utiliser une variable globale pour les singletons.

private let _singletonInstance = SingletonClass()
class SingletonClass {
  class var sharedInstance: SingletonClass {
    return _singletonInstance
  }
}

Comme mentionné dans l'article du blog Swift ici:

L'initialisateur paresseux pour une variable globale (également pour les membres statiques de   structs et enums) est exécuté la première fois que global est accédé, et   est lancé en tant que dispatch_once pour s'assurer que l'initialisation est   atomique. Cela permet une manière cool d'utiliser dispatch_once dans votre code:   il suffit de déclarer une variable globale avec un initialiseur et de la marquer   privé.

Cette façon de créer un singleton est sûre, rapide, paresseuse, et également connectée à ObjC gratuitement.


160
2018-02-10 16:03



Swift 1.2 ou version ultérieure prend désormais en charge les variables / constantes statiques dans les classes. Vous pouvez donc simplement utiliser une constante statique:

class MySingleton {

    static let sharedMySingleton = MySingleton()

    private init() {
        // ...
    }
}

45
2018-06-05 18:02



Il y a une meilleure façon de le faire. Vous pouvez déclarer une variable globale dans votre classe au-dessus de la déclération de classe comme si

var tpScopeManagerSharedInstance = TPScopeManager()

Cela appelle simplement votre init par défaut ou n'importe quelles variables init et globales sont dispatch_once par défaut dans Swift. Ensuite, quelle que soit la classe dans laquelle vous voulez obtenir une référence, faites simplement ceci:

var refrence = tpScopeManagerSharedInstance
// or you can just access properties and call methods directly
tpScopeManagerSharedInstance.someMethod()

Donc, fondamentalement, vous pouvez vous débarrasser du bloc entier de code d'instance partagée.


32
2018-01-13 03:36



Les singletons rapides sont exposés dans les cadres de Cocoa en tant que fonctions de classe, par ex. NSFileManager.defaultManager(), NSNotificationCenter.defaultCenter(), donc je pense qu'il est plus logique en tant que fonction de classe de refléter ce comportement, plutôt qu'une variable de classe comme d'autres solutions utilisent, par exemple.

class MyClass {

    private static let _sharedInstance = MyClass()

    class func sharedInstance() -> MyClass {
        return _sharedInstance
    }
}

Récupérer le singleton via MyClass.sharedInstance().


27
2017-09-09 12:51



Swift 4+

protocol Singleton: class {
    static var sharedInstance: Self { get }
}

final class Kraken: Singleton {
    static let sharedInstance = Kraken()
    private init() {}
}

15
2017-09-18 07:31



Par le Documentation Apple, il a été répété plusieurs fois que la façon la plus simple de le faire dans Swift est avec une propriété de type statique:

class Singleton {
    static let sharedInstance = Singleton()
}

Cependant, si vous cherchez un moyen d'effectuer une configuration supplémentaire au-delà d'un simple appel de constructeur, le secret est d'utiliser une fermeture invoquée immédiatement:

class Singleton {
    static let sharedInstance: Singleton = {
        let instance = Singleton()
        // setup code
        return instance
    }()
}

Ceci est garanti pour être thread-safe et paresseusement initialisé qu'une seule fois.


14
2018-06-05 02:29



En regardant l'exemple de code d'Apple, je suis tombé sur ce modèle. Je ne suis pas sûr de savoir comment Swift traite les statistiques, mais ce serait thread safe en C #. J'inclus à la fois la propriété et la méthode d'interopérabilité Objective-C.

struct StaticRank {
    static let shared = RankMapping()
}

class func sharedInstance() -> RankMapping {
    return StaticRank.shared
}

class var shared:RankMapping {
    return StaticRank.shared
}

9
2018-06-15 16:28