Utilisation d'un modèle singleton dispatch_once dans Swift


575

J'essaie de trouver un modèle singleton approprié à utiliser 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!
    }
}

L'encapsulation de l'instance singleton dans la structure statique devrait permettre une seule instance qui n'entre pas en collision avec 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. J'ai donc 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 j'obtiens une erreur de compilation sur la dispatch_onceligne:

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

J'ai essayé plusieurs variantes différentes 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_onceSwift? Au début, je pensais que le problème était avec le bloc en raison du ()message d'erreur, mais plus je le regarde, plus je pense qu'il peut être question d'obtenir le dispatch_once_tcorrectement défini.


3
Je supprimerais tout ce code statique et utiliserais une propriété en lecture seule avec un initialiseur @lazy.
Sulthan

1
C'est ce que je voulais dire. Malheureusement, nous n'avons toujours pas assez d'informations sur les internes. Cependant, à mon humble avis, toute mise en œuvre de @lazydevrait être thread-safe.
Sulthan

1
Et cette manière présente également l'avantage de ne pas exposer l'implémentation aux prédations des appelants.
David Berry

1
Il ne semble pas non plus que vous puissiez avoir des variables de classe @lazy.
David Berry

Faites attention! Deux choses à noter avec cette approche. Tout d'abord, toutes les classes qui en hériteront devront remplacer la propriété sharedInstance. Static.instance = TPScopeManager()force le type d'instance. Si vous utilisez quelque chose comme Static.instance = self()avec un initialiseur requis, la classe de type appropriée sera générée. Même ainsi, et c'est la chose importante à noter, une seule fois pour toutes les instances de la hiérarchie! Le premier type à initialiser est le type défini pour toutes les instances. Je ne pense pas que l'objectif-c se soit comporté de la même façon.
sean woodward

Réponses:


713

tl; dr: utilisez l' approche constante de classe si vous utilisez Swift 1.2 ou supérieur et l' approche structurée imbriquée si vous devez prendre en charge les 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 paresseusement les constantes de classe (et les variables) et est thread-safe par la définition de let. Il s'agit désormais de la méthode officiellement recommandée 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 structurée 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 comme 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 Objective-C traditionnelle portait sur Swift. Je suis assez certain qu'il n'y a aucun avantage sur l'approche de structure imbriquée mais je le mets quand même ici 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.


13
"thread safe en vertu de let" - cela a-t-il été dit quelque part? Je ne trouve pas de mention dans la documentation.
jtbandes

4
@jtbandes Les constantes sont thread-safe dans toutes les langues que je connais.
hpique

2
@DaveWood Je suppose que vous parlez de la dernière approche. Je citerai moi-même: "Je dirais qu'il n'est plus nécessaire d'utiliser cette approche mais je la mets quand même ici car je trouve les différences de syntaxe intéressantes."
hpique

5
Doit- initil également être déclaré privatepour garantir qu'une et une seule instance de l'objet existera pendant la durée de vie de l'application?
Andrew

5
Dans l'approche "Constante de classe", je suggère (a) de déclarer la classe pour finalne pas la sous- classer ; et (b) déclarer la initméthode pour privateque vous ne puissiez pas instancier accidentellement une autre instance quelque part.
Rob

175

Depuis qu'Apple a maintenant clarifié que les variables structurées statiques sont initialisées à la fois paresseuses et enveloppées dispatch_once(voir la note à la fin de l'article), je pense que ma solution finale sera:

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

        return Singleton.instance
    }
}

Cela profite de l'initialisation automatique paresseuse et thread-safe des éléments de structure statiques, cache en toute sécurité l'implémentation réelle du consommateur, conserve tout compactement compartimenté pour la lisibilité et élimine une variable globale visible.

Apple a précisé que les initialiseurs paresseux sont thread-safe, il n'y a donc pas besoin de dispatch_onceprotections similaires

L'initialiseur paresseux d'une variable globale (également pour les membres statiques des structures et des énumérations) 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 intéressante d'utiliser dispatch_once dans votre code: déclarez simplement une variable globale avec un initialiseur et marquez-la comme privée.

D' ici


1
Pour confirmer: les variables globales ont une initialisation paresseuse et thread-safe, mais pas les variables de classe. Droite?
Bill

14
J'ajouterais qu'une bonne pratique serait de déclarer l'initialiseur comme privé:, private init() {}pour renforcer davantage le fait que cette classe n'est pas destinée à être instanciée extérieurement.
Pascal Bourque

1
donc l'initialisation de la structure statique var est paresseuse et sûre pour les threads, que se passe-t-il si cette structure statique var est un dictionnaire pour multitons, alors nous devons synchroniser / mettre en file d'attente manuellement les appels pour chaque accès, non?

Si je comprends bien votre question, les accès aux dictionnaires et aux tableaux ne sont pas intrinsèquement sûrs pour les threads, vous devrez donc utiliser une certaine forme de synchronisation des threads.
David Berry

@DavidBerry Comment dois-je appeler une fonction à l'intérieur de cette classe singleton? J'ai besoin d'une fonction pour être appelée lors du premier appel de myClass.sharedInstance.
Ameet Dhas

163

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 guère de raison maintenant d'utiliser l'une des méthodes précédentes pour les singletons.

Mise à jour : c'est maintenant la façon officielle de définir les singletons comme décrit dans les documents officiels !

En ce qui concerne les préoccupations sur l' utilisation staticvs class. staticdevrait être celui à utiliser même lorsque les classvariables deviennent disponibles. Les singletons ne sont pas censés être sous-classés car cela entraînerait plusieurs instances du singleton de base. L'utilisation staticapplique cela d'une manière magnifique et Swifty.

Pour Swift 1.0 et 1.1:

Avec les récents changements dans Swift, principalement de nouvelles méthodes de contrôle d'accès, je penche maintenant vers une manière 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'initialiseur paresseux d'une variable globale (également pour les membres statiques des structures et des énumérations) 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 intéressante d'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ûre pour les threads, rapide, paresseuse et également reliée à ObjC gratuitement.


2
Quiconque ne lit que cette réponse: n'oubliez pas de rendre le jeton statique, sinon le comportement n'est pas défini. Voir la question modifiée de David pour le code complet.
nschum

@nschum sinon, le comportement n'est pas indéfini, il est juste cassé de manière bien définie: le bloc s'exécutera toujours.
Michael

@Michael: La documentation indique qu'elle n'est pas définie. Le comportement actuel est donc fortuit.
nschum

1
C'est une chose étrange à dire. Si la documentation l'appelle "non défini", cela signifie simplement que celui qui a écrit le code ne fait aucune promesse quant à ce qu'il fait. Cela n'a rien à voir avec le code sachant si la variable est statique. Cela signifie simplement que le comportement actuel (ou apparent) ne peut pas être invoqué.
nschum

6
Vous voudrez peut-être ajouter private init() {}comme initialiseur de SingletonClass. pour éviter d'instancier de l'extérieur.
rintaro

46

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() {
        // ...
    }
}

35

Il existe une meilleure façon de procéder. Vous pouvez déclarer une variable globale dans votre classe au-dessus de la déclaration de classe comme ceci:

var tpScopeManagerSharedInstance = TPScopeManager()

Cela appelle simplement votre init par défaut ou les variables init et globales dispatch_oncepar défaut dans Swift. Ensuite, dans la classe pour laquelle vous souhaitez obtenir une référence, il vous suffit de faire ceci:

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

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


3
Pourquoi un "var" et beaucoup un "let"?
Stephan

1
pourrait peut-être être loué, je ne l'ai testé qu'avec un var.
Kris Gellci

J'aime cette réponse, mais je dois y accéder (Singleton) depuis Interface Builder. Toute idée sur la façon d'accéder à ce tpScopeManagerSharedInstance depuis IB?. Merci.-
Luis Palacios

C'est ma façon préférée d'avoir un singleton. Il a toutes les fonctionnalités habituelles (sécurité des threads et instanciation paresseuse) et il prend en charge une syntaxe très légère: pas besoin d'écrire TPScopeManager.sharedInstance.doIt()tout le temps, il suffit de nommer votre classe TPScopeManagerClass, d'avoir cette déclaration à côté de la classe public let TPScopeManager = TPScopeManagerClass(), et lorsque vous utilisez simplement write TPScopeManager.doIt(). Très propre!
Alex

Il n'y a rien ici pour empêcher la création d'instances supplémentaires de TPScopeManager, et ce n'est donc pas un singleton par définition.
Caleb

28

Singletons Swift sont exposés dans les cadres de cacao comme des fonctions de classe, par exemple NSFileManager.defaultManager(), NSNotificationCenter.defaultCenter(). Il est donc plus logique en tant que fonction de classe de refléter ce comportement, plutôt qu'en tant que variable de classe comme 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().


1
voté pour le commentaire de LearnCocos2D :), également pour le style.
x4h1d

2
la variable globale doit être changée en une variable de classe via un statique à l'intérieur de la classe.
malhal

2
@malhal lorsqu'une variable est marquée privée mais en dehors d'une classe, elle n'est pas globale - mais limitée au fichier dans lequel elle se trouve. Une statique à l'intérieur de la classe fonctionnerait à peu près de la même manière, mais j'ai mis à jour la réponse pour utiliser la statique comme vous l'avez suggéré, car il regroupe mieux la variable dans la classe s'il vous arrive d'utiliser plusieurs classes dans le fichier.
Ryan

1
"Les Swift Singletons sont exposés dans les frameworks cacao en tant que fonctions de classe" ... Pas dans Swift 3. Ce sont désormais généralement des staticpropriétés.
Rob

17

Selon la documentation Apple , il a été répété à plusieurs reprises 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 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 initialisé paresseusement une seule fois.


comment pouvez-vous remettre l'instance de let statique à zéro?
gpichler

1
@ user1463853 - Vous ne pouvez pas, et ne devriez généralement pas.
Rob

16

Swift 4+

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

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

2
cela a besoin de classe finale, pouvez-vous expliquer plus la différence, parce que j'ai un problème avec l'autre solution de singleton avec struct
Raheel Sadiq

si cela doit être un remplacement privé init () {}
NSRover

8

En regardant l'exemple de code d'Apple, je suis tombé sur ce modèle. Je ne sais pas comment Swift gère la statique, mais ce serait sûr pour les threads 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
}

Je suis sûr que l'utilisation de cette syntaxe statique par défaut fera tous les travaux ennuyeux.
Eonil

malheureusement, la statique ne fonctionne qu'à l'intérieur des structures, c'est pourquoi ce modèle.
user2485100

Mon intention était de ne pas avoir à utiliser d' dispatch_onceétoffes. Je parie sur ton style. :)
Eonil

N'est-ce pas classdans une déclaration de classe l'équivalent d' staticune déclaration struct?
Russell Borogove

@Sam Oui, c'est ça. Voir l'entrée de blog Apple sur les fichiers et l'initialisation qui indique clairement que les membres globaux et statiques des structures et des énumérations bénéficient de cette dispatch_oncecapacité.
Rob

5

En bref,

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

Vous voudrez peut-être lire les fichiers et l'initialisation

L'initialiseur paresseux pour une variable globale (également pour les membres statiques des structures et des énumérations) est exécuté la première fois que global est accédé et est lancé dispatch_oncepour s'assurer que l'initialisation est atomique.


4

Si vous prévoyez d'utiliser votre classe Swift singleton dans Objective-C, cette configuration fera en sorte que le compilateur génère des en-têtes appropriés de type Objective-C:

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

Ensuite, dans la classe Objective-C, vous pouvez appeler votre singleton comme vous l'avez fait dans les jours pré-Swift:

[ImageStore sharedStore];

Ceci est juste ma mise en œuvre simple.


C'est en fait plus concis et correct que l'autre exemple car il est implémenté de la même manière que les autres singletons Swift. c'est-à-dire: comme les fonctions de classe comme NSFileManager.defaultManager(), mais utilisent toujours les mécanismes de membre statique paresseux thread-safe de Swift.
Leslie Godwin

Cocoa les implémente généralement en tant que propriétés statiques, et non en tant que fonctions de classe.
Rob

J'en suis conscient, mon commentaire a plus de 2 ans. Merci d'avoir mentionné.
Michael

4

Première solution

let SocketManager = SocketManagerSingleton();

class SocketManagerSingleton {

}

Plus loin 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 les accolades pour moins de confusion:

func someFunction() {        
    var socketManager = SocketManager()        
}

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

Appelez-le ensuite;

let shared = MySingleton.shared

Bravo non seulement pour marquer initcomme private, mais aussi pour faire le sharedMyModelas final! Pour le plaisir des futurs lecteurs, dans Swift 3, nous pourrions être enclins à renommer sharedMyModelsimplement shared.
Rob

C'est la seule réponse correcte, sauf que la substitution et l'appel à super.init sont erronés et ne seront même pas compilés.
Michael Morris

4

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)")

C'est exactement la même chose que l'une des réponses que j'ai traversées sur le chemin de la réponse actuelle. Étant donné que les variables globales sont initialisées à la fois paresseuses et thread-safe, il n'y a aucune raison de la complexité supplémentaire.
David Berry

@David Autre que de ne pas avoir de variable globale. :)
hpique

@hpique non, exactement comme l'une de mes tentatives précédentes. Regardez l'historique des modifications.
David Berry

4

La meilleure approche dans Swift au-dessus de 1.2 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 ce lien .


Pourquoi une NSObjectsous - classe?. En dehors de cela, cela semble être essentiellement le même que stackoverflow.com/a/28436202/1187415 .
Martin R

3

Depuis Apple Docs (Swift 3.0.1),

Vous pouvez simplement utiliser une propriété de type statique, qui est garantie d'être initialisée paresseusement une seule fois, même en cas d'accès simultané sur 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

Je suggérerais un enum, comme vous utiliseriez en Java, par exemple

enum SharedTPScopeManager: TPScopeManager {
    case Singleton
}

OMI, c'est la seule manière Swift correcte d'implémenter Singleton. les autres réponses sont de la manière ObjC / C / C ++
Bryan Chen

Pourriez-vous développer cette réponse? Il n'est pas clair pour moi où Singleton est instancié à partir de cet extrait
Kenny Winker

@KennyWinker Je n'ai pas de connexion de développeur Apple, donc pas rapide et je ne peux donc pas répondre lorsque l'initialisation se produit. En Java, c'est à la première utilisation. Peut-être pourriez-vous l'essayer avec une impression à l'initialisation et voir si l'impression se produit au lancement ou après l'accès. Cela dépendra de la façon dont l'énumération est implémentée par le compilateur.
Howard Lovatt

@KennyWinkler: Apple vient de clarifier comment cela fonctionne, voir developer.apple.com/swift/blog/?id=7 . Ils y disent "exécutez l'initialiseur pour un global la première fois qu'il est référencé, similaire à Java" et en particulier. Ils disent également que sous les couvertures, ils utilisent "dispatch_once pour s'assurer que l'initialisation est atomique". Par conséquent, l'énumération est presque certainement la voie à suivre, à moins que vous n'ayez quelque init de fantaisie à faire, alors un let statique privé est la solution.
Howard Lovatt

2

Juste pour 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 pu trouver cet exemple complet, alors j'espère que cela aide 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 Swift vivant 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

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

Je préfère cette implémentation:

class APIClient {

}

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

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

1

Ma façon d'implémenter 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 ce qui suit.

Lis:

 println(ConfigurationManager.sharedInstance.globalDic)  

Écrire:

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

1

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

Les raisons:

  • static La propriété de type est garantie d'être initialisée paresseusement une seule fois, même lorsqu'elle est accessible sur plusieurs threads simultanément, donc pas besoin d'utiliser dispatch_once
  • Privatiser la initméthode afin que l'instance ne puisse pas être créée par d'autres classes.
  • final car vous ne voulez pas que d'autres classes héritent de la classe Singleton.

Pourquoi avez-vous utilisé l'initialisation de la fermeture alors que vous pouvez utiliser directementstatic let sharedInstance = Singleton()
abhimuralidharan

1
si vous ne voulez pas faire de configuration supplémentaire, alors ce que vous dites est juste.
applefreak

1

Après avoir vu l'implémentation de David, il semble qu'il n'y ait pas besoin d'avoir une fonction de classe singleton instanceMethodcar letcela fait à peu près la même chose qu'une sharedInstanceméthode de classe. Tout ce que vous avez à faire est de le déclarer comme une constante globale et ce serait tout.

let gScopeManagerSharedInstance = ScopeManager()

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

2
Comme je le dis dans mes commentaires, la seule raison de le faire est qu'à un moment donné dans le futur, vous pouvez déplacer / masquer la variable globale et obtenir un comportement plus semblable à un singleton. À ce stade, si tout utilise un modèle cohérent, vous pouvez simplement changer les classes singleton elles-mêmes sans avoir à changer l'utilisation.
David Berry

0
   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!
}

Comme cela a été longuement discuté ici, il n'est pas nécessaire de boucler rapidement l'initialisation dispatch_oncecar l'initialisation des variables statiques est paresseuse et automatiquement protégée via dispatch_once Apple recommande en fait d'utiliser la statique au lieu de dispatch_once pour cette raison.
David Berry du

0

Swift pour réaliser singleton dans le passé, n'est rien de plus que les trois façons: les variables globales, les variables internes et les méthodes dispatch_once.

Voici deux bons singleton. (Remarque: quel que soit le type d'écriture, il faudra prêter attention à la méthode de privatisation init () .Parce que dans Swift, tout le constructeur par défaut de l'objet est public, doit être réécrit init peut être transformé en privé , empêche les autres objets de cette classe '()' par 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

-1

C'est le plus simple avec des capacités thread-safe. Aucun autre thread ne peut accéder au même objet singleton même s'il le souhaite. Swift 3/4

struct DataService {

    private static var _instance : DataService?

    private init() {}   //cannot initialise from outer class

    public static var instance : DataService {
        get {
            if _instance == nil {
                DispatchQueue.global().sync(flags: .barrier) {
                    if _instance == nil {
                        _instance = DataService()
                    }
                }
            }
            return _instance!
        }
    }
}

2
Quel est l'avantage par rapport à une propriété de type statique (qui est garantie d'être initialisée paresseusement une seule fois, même en cas d'accès simultané sur plusieurs threads)?
Martin R

-1

J'ai exigé que mon singleton autorise l'héritage, et aucune de ces solutions ne le permettait réellement. J'ai donc trouvé ceci:

public class Singleton {
    private static var sharedInstanceVar = Singleton()

    public class func sharedInstance() -> Singleton {
        return sharedInstanceVar
    }
}


public class SubSingleton: Singleton {

    private static var sharedInstanceToken: dispatch_once_t = 0

    public class override func sharedInstance() -> SubSingleton {
        dispatch_once(&sharedInstanceToken) {
            sharedInstanceVar = SubSingleton()
        }
    return sharedInstanceVar as! SubSingleton
    }
}
  • De cette façon, lors de la Singleton.sharedInstance()première exécution, il retournera l'instance deSingleton
  • Lors de la SubSingleton.sharedInstance()première, il retournera l'instance de SubSingletoncreated.
  • Si ce qui précède est fait, alors SubSingleton.sharedInstance()est Singletonest vrai et la même instance est utilisé.

Le problème avec cette première approche sale est que je ne peux pas garantir que les sous-classes implémenteront le dispatch_once_tet s'assurer qu'il sharedInstanceVarn'est modifié qu'une fois par classe.

J'essaierai de l'affiner davantage, mais il serait intéressant de voir si quelqu'un a de forts sentiments contre cela (outre le fait qu'il est verbeux et nécessite de le mettre à jour manuellement).



-2

J'utilise la syntaxe suivante:

public final class Singleton {    
    private class func sharedInstance() -> Singleton {
        struct Static {
            //Singleton instance.
            static let sharedInstance = Singleton()
        }
        return Static.sharedInstance
    }

    private init() { }

    class var instance: Singleton {
        return sharedInstance()
    }
}

Cela fonctionne de Swift 1.2 à 4, et présente plusieurs avantages:

  1. Rappelle à l'utilisateur de ne pas implémenter la sous-classe
  2. Empêche la création d'instances supplémentaires
  3. Assure la création paresseuse et l'instanciation unique
  4. Raccourcit la syntaxe (évite ()) en permettant d'accéder à l'instance en tant que Singleton.instance
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.