web-dev-qa-db-fra.com

Utilisation d'un modèle singleton dispatch_once dans Swift

J'essaie d'élaborer un modèle de singleton approprié pour une utilisation dans Swift. Jusqu'à présent, j'ai pu obtenir un modèle sans thread thread qui fonctionne 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 Static devrait permettre une instance unique qui ne se heurterait pas à des instances singleton sans schémas de nommage complexes, et devrait rendre les choses assez privées. Évidemment, ce modèle n’est pas thread-safe, alors j’ai essayé d’ajouter dispatch_once à tout cela:

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 compilation sur la ligne dispatch_once:

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

J'ai essayé plusieurs variantes de la syntaxe, mais elles semblent toutes avoir les mêmes résultats:

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

Quelle est la bonne utilisation de dispatch_once en utilisant Swift? Au début, je pensais que le problème était lié au bloc en raison du () dans le message d'erreur, mais plus je le regarde, plus je pense que le problème est peut-être de définir correctement le dispatch_once_t.

554
David Berry

tl; dr: utilisez l’approche de la classe constante si vous utilisez Swift 1.2 ou une version ultérieure, et l’option approche imbriquée de la structure si vous devez prendre en charge des versions antérieures.

D'après mon expérience avec Swift, il existe trois approches pour implémenter le modèle Singleton qui prennent en charge 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, car Swift initialise les constantes de classe (et les variables) de manière nonchalante. Elle est également thread-safe grâce à la définition de let. C’est maintenant de manière officielle pour 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.

Struct imbriqué

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

Nous utilisons ici la constante statique d'une structure imbriquée en tant que constante de classe. Il s'agit d'une solution de contournement pour le manque de constantes de classe statiques dans Swift 1.1 et versions antérieures, et fonctionne toujours comme solution de contournement pour le manque de constantes statiques et de variables dans les fonctions.

dispatch_once

L’approche traditionnelle Objective-C a été portée à Swift. Je suis presque certain qu'il n'y a pas d'avantage sur l'approche de structure imbriquée, mais je la pose quand même 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!
    }
}

Voir ce projet GitHub pour les tests unitaires.

697
hpique

Depuis qu'Apple a clarifié que les variables de structure statiques sont initialisées à la fois paresseuses et enveloppées dans dispatch_once (voir la note à la fin du post), je pense que ma solution finale sera la suivante:

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

        return Singleton.instance
    }
}

Ceci tire parti de l'initialisation automatique paresseuse et sécurisée des threads des éléments stat statiques, masque en toute sécurité l'implémentation réelle pour le consommateur, maintient le compartiment de manière compacte pour la lisibilité et élimine une variable globale visible.

Apple a précisé que l'initialiseur différé est thread-safe, il n'est donc pas nécessaire d'utiliser dispatch_once ou des protections similaires.

L'initialiseur différé pour une variable globale (également pour les membres statiques de structures et d'énums) est exécuté lors du premier accès à global et est lancé en tant que dispatch_once pour s'assurer que l'initialisation est atomique. Cela permet une méthode intéressante pour utiliser dispatch_once dans votre code: déclarez simplement une variable globale avec un initialiseur et marquez-la comme privée.

De ici

173
David Berry

Pour Swift 1.2 et au-delà:

class Singleton  {
   static let sharedInstance = Singleton()
}

Avec une preuve d'exactitude (tout le mérite revient ici ), il n'y a pratiquement aucune raison d'utiliser les méthodes précédentes pour les singletons.

Mise à jour : C’est maintenant le moyen officiel de définir des singletons comme décrit dans les documents officiels !

En ce qui concerne les préoccupations sur l'utilisation de static vs class. static devrait être celui à utiliser même lorsque les variables class deviennent disponibles. Les singletons ne sont pas censés être sous-classés, car cela entraînerait plusieurs instances du singleton de base. Utiliser static l'applique de manière magnifique et rapide.

Pour Swift 1.0 et 1.1:

Avec les récents changements apportés à Swift, principalement de nouvelles méthodes de contrôle d'accès, je m'oriente maintenant vers une méthode plus propre d'utilisation d'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'initialiseur différé pour une variable globale (également pour les membres statiques de structures et d'énums) est exécuté lors du premier accès à global et est lancé en tant que dispatch_once pour s'assurer que l'initialisation est atomique. Cela permet une méthode intéressante pour utiliser dispatch_once dans votre code: déclarez simplement une variable globale avec un initialiseur et marquez-la comme privée.

Cette façon de créer un singleton est sécurisée pour les threads, rapide, paresseuse et également pontée gratuitement vers ObjC.

162
Jack

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

class MySingleton {

    static let sharedMySingleton = MySingleton()

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

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écélération de la classe, comme ceci

var tpScopeManagerSharedInstance = TPScopeManager()

Ceci appelle simplement votre init par défaut ou n'importe laquelle des variables init et globales sont dispatch_once par défaut dans Swift. Ensuite, dans la classe pour laquelle vous souhaitez obtenir une référence, procédez comme suit:

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

Donc, en gros, vous pouvez vous débarrasser de tout le bloc de code d'instance partagée.

33
Kris Gellci

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

class MyClass {

    private static let _sharedInstance = MyClass()

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

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

27
Ryan

Swift 4 +

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

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

D'après documentation Apple , il a été répété à maintes reprises que le moyen le plus simple de procéder dans Swift consiste à utiliser une propriété de type statique:

class Singleton {
    static let sharedInstance = Singleton()
}

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

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

Ceci est garanti pour être thread-safe et paralysé une seule fois.

16
Adrian Macneil

En regardant le code exemple d'Apple, je suis tombé sur ce modèle. Je ne suis pas sûr de savoir comment Swift gère la statique, mais cela serait sans danger en C #. J'inclus à la fois la propriété et la méthode pour interop Objective-C.

struct StaticRank {
    static let shared = RankMapping()
}

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

class var shared:RankMapping {
    return StaticRank.shared
}
8
user2485100

En bref,

class Manager {
    static let sharedInstance = Manager()
    private init() {}
}

Vous voudrez peut-être lire fichiers et initialisation

L'initialiseur différé pour une variable globale (également pour les membres statiques de structures et d'énums) est exécuté lors du premier accès à global et est lancé en tant que dispatch_once pour s'assurer que l'initialisation est atomique.

5
onmyway133

Si vous prévoyez d'utiliser votre classe singleton Swift en Objective-C, le compilateur générera le ou les en-têtes appropriés de type Objective-C:

class func sharedStore() -> ImageStore {
struct Static {
    static let instance : ImageStore = ImageStore()
    }
    return Static.instance
}

Ensuite, en classe Objective-C, vous pouvez appeler votre singleton de la même manière que vous l'aviez fait avant Swift:

[ImageStore sharedStore];

Ceci est juste ma simple implémentation.

4
Michael
final class MySingleton {
     private init() {}
     static let shared = MySingleton()
}

Alors appelle ça;

let shared = MySingleton.shared
4
Kemal Can Kaynak

Première solution

let SocketManager = SocketManagerSingleton();

class SocketManagerSingleton {

}

Plus tard dans votre code:

func someFunction() {        
    var socketManager = SocketManager        
}

Deuxième solution

func SocketManager() -> SocketManagerSingleton {
    return _SocketManager
}
let _SocketManager = SocketManagerSingleton();

class SocketManagerSingleton {

}

Et plus tard dans votre code, vous pourrez garder des accolades pour moins de confusion:

func someFunction() {        
    var socketManager = SocketManager()        
}
4
Nicolas Manzini

Utilisation:

class UtilSingleton: NSObject {

    var iVal: Int = 0

    class var shareInstance: UtilSingleton {
        get {
            struct Static {
                static var instance: UtilSingleton? = nil
                static var token: dispatch_once_t = 0
            }
            dispatch_once(&Static.token, {
                Static.instance = UtilSingleton()
            })
            return Static.instance!
        }
    }
}

Comment utiliser:

UtilSingleton.shareInstance.iVal++
println("singleton new iVal = \(UtilSingleton.shareInstance.iVal)")
4
Kingiol

Au-dessus de 1.2, la meilleure approche dans Swift est un singleton d’une ligne, comme -

class Shared: NSObject {

    static let sharedInstance = Shared()

    private override init() { }
}

Pour en savoir plus sur cette approche, vous pouvez visiter ceci lien .

4
CodeCracker

Je suggérerais un Enum, comme vous le feriez avec Java, par exemple:

enum SharedTPScopeManager: TPScopeManager {
  case Singleton
}
3
Howard Lovatt

De la part d'Apple Documents (Swift 3.0.1),

Vous pouvez simplement utiliser une propriété de type statique qui ne peut être initialisé qu'une seule fois, même en cas d'accès simultané à plusieurs threads:

class Singleton {
    static let sharedInstance = Singleton()
}

Si vous devez effectuer une configuration supplémentaire au-delà de l'initialisation, vous pouvez affecter le résultat de l'appel d'une fermeture à la constante globale:

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

La seule bonne approche est ci-dessous

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

    private init() {}
}

Accéder

let signleton = Singleton.sharedInstance

Raisons:

  • il est garanti que la propriété de type static est paresseusement initialisée une seule fois, même en cas d'accès simultané à plusieurs threads. Inutile donc d'utiliser dispatch_once.
  • Privatiser la méthode init afin que l'instance ne puisse pas être créée par d'autres classes.
  • classe finale car vous ne voulez pas que d'autres classes héritent de la classe Singleton
2
applefreak

À titre de référence, voici un exemple d'implémentation Singleton de l'implémentation Nested Struct de Jack Wu/hpique. L'implémentation montre également comment l'archivage pourrait fonctionner, ainsi que certaines fonctions associées. Je n'ai pas trouvé cet exemple complet, alors j'espère que cela aidera quelqu'un!

import Foundation

class ItemStore: NSObject {

    class var sharedStore : ItemStore {
        struct Singleton {
            // lazily initiated, thread-safe from "let"
            static let instance = ItemStore()
        }
        return Singleton.instance
    }

    var _privateItems = Item[]()
    // The allItems property can't be changed by other objects
    var allItems: Item[] {
        return _privateItems
    }

    init() {
        super.init()
        let path = itemArchivePath
        // Returns "nil" if there is no file at the path
        let unarchivedItems : AnyObject! = NSKeyedUnarchiver.unarchiveObjectWithFile(path)

        // If there were archived items saved, set _privateItems for the shared store equal to that
        if unarchivedItems {
            _privateItems = unarchivedItems as Array<Item>
        } 

        delayOnMainQueueFor(numberOfSeconds: 0.1, action: {
            assert(self === ItemStore.sharedStore, "Only one instance of ItemStore allowed!")
        })
    }

    func createItem() -> Item {
        let item = Item.randomItem()
        _privateItems.append(item)
        return item
    }

    func removeItem(item: Item) {
        for (index, element) in enumerate(_privateItems) {
            if element === item {
                _privateItems.removeAtIndex(index)
                // Delete an items image from the image store when the item is 
                // getting deleted
                ImageStore.sharedStore.deleteImageForKey(item.itemKey)
            }
        }
    }

    func moveItemAtIndex(fromIndex: Int, toIndex: Int) {
        _privateItems.moveObjectAtIndex(fromIndex, toIndex: toIndex)
    }

    var itemArchivePath: String {
        // Create a filepath for archiving
        let documentDirectories = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
        // Get the one document directory from that list
        let documentDirectory = documentDirectories[0] as String
        // append with the items.archive file name, then return
        return documentDirectory.stringByAppendingPathComponent("items.archive")
    }

    func saveChanges() -> Bool {
        let path = itemArchivePath
        // Return "true" on success
        return NSKeyedArchiver.archiveRootObject(_privateItems, toFile: path)
    }
}

Et si vous n'avez pas reconnu certaines de ces fonctions, voici un petit fichier utilitaire vivant de Swift que j'utilise:

import Foundation
import UIKit

typealias completionBlock = () -> ()

extension Array {
    func contains(#object:AnyObject) -> Bool {
        return self.bridgeToObjectiveC().containsObject(object)
    }

    func indexOf(#object:AnyObject) -> Int {
        return self.bridgeToObjectiveC().indexOfObject(object)
    }

    mutating func moveObjectAtIndex(fromIndex: Int, toIndex: Int) {
        if ((fromIndex == toIndex) || (fromIndex > self.count) ||
            (toIndex > self.count)) {
                return
        }
        // Get object being moved so it can be re-inserted
        let object = self[fromIndex]

        // Remove object from array
        self.removeAtIndex(fromIndex)

        // Insert object in array at new location
        self.insert(object, atIndex: toIndex)
    }
}

func delayOnMainQueueFor(numberOfSeconds delay:Double, action closure:()->()) {
    dispatch_after(
        dispatch_time(
            DISPATCH_TIME_NOW,
            Int64(delay * Double(NSEC_PER_SEC))
        ),
        dispatch_get_main_queue()) {
            closure()
    }
}
2
SchoonSauce

Après avoir vu l'implémentation de David, il semble inutile de disposer d'une fonction de classe singleton instanceMethod puisque let fait à peu près la même chose qu'une méthode de classe sharedInstance. Tout ce que vous avez à faire est de le déclarer comme une constante globale et ce serait bien.

let gScopeManagerSharedInstance = ScopeManager()

class ScopeManager {
 // No need for a class method to return the shared instance. Use the gScopeManagerSharedInstance directly. 
}
1
Essa A. Haddad

Je préfère cette implémentation:

class APIClient {

}

var sharedAPIClient: APIClient = {
    return APIClient()
}()

extension APIClient {
    class func sharedClient() -> APIClient {
        return sharedAPIClient
    }
}
1
Viktor Radchenko

Ma façon de mettre en œuvre dans Swift ...

ConfigurationManager.Swift

import Foundation

    let ConfigurationManagerSharedInstance = ConfigurationManager()
 class ConfigurationManager : NSObject {
    var globalDic: NSMutableDictionary = NSMutableDictionary()

class var sharedInstance:ConfigurationManager {
    return ConfigurationManagerSharedInstance

}

init() {

    super.init()

    println ("Config Init been Initiated, this will be called only onece irrespective of many calls")   

}

Accédez au globalDic à partir de n’importe quel écran de l’application par l’installation ci-dessous.

Lis:

 println(ConfigurationManager.sharedInstance.globalDic)  

Écrire:

 ConfigurationManager.sharedInstance.globalDic = tmpDic // tmpDict is any value that to be shared among the application
1
user2737730

Dans Swift, vous pouvez créer une classe singleton de la manière suivante:

class AppSingleton: NSObject {

    //Shared instance of class
    static let sharedInstance = AppSingleton()

    override init() {
        super.init()
    }
}
1
Vicky Prajapati
   func init() -> ClassA {
    struct Static {
        static var onceToken : dispatch_once_t = 0
        static var instance : ClassA? = nil
    }

    dispatch_once(&Static.onceToken) {
        Static.instance = ClassA()
    }

    return Static.instance!
}
0
DD.amor

Rapide pour réaliser un singleton dans le passé, n’est rien de plus que les trois manières: variables globales, variables internes et méthodes dispatch_once.

Voici deux bons singleton. (Note: quel que soit le type d’écriture, il faut prêter attention à la méthode de privatisation init () .Parce que dans Swift, tout le constructeur par défaut de l’objet est public, il doit être réécrit. Init peut être transformé en privé. , empêche les autres objets de cette classe '()' par la méthode d’initialisation par défaut de créer l’objet.)

Méthode 1:

class AppManager {
    private static let _sharedInstance = AppManager()

    class func getSharedInstance() -> AppManager {
       return _sharedInstance
    }

    private init() {} // Privatizing the init method
}

// How to use?
AppManager.getSharedInstance()

Méthode 2:

class AppManager {
    static let sharedInstance = AppManager()

    private init() {} // Privatizing the init method
}

// How to use?
AppManager.sharedInstance
0
Tim