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
.
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.
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.
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.
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.
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
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.
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() {
// ...
}
}
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.
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()
.
Swift 4 +
protocol Singleton: class {
static var sharedInstance: Self { get }
}
final class Kraken: Singleton {
static let sharedInstance = Kraken()
private init() {}
}
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.
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
}
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.
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.
final class MySingleton {
private init() {}
static let shared = MySingleton()
}
Alors appelle ça;
let shared = MySingleton.shared
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()
}
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)")
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 .
Je suggérerais un Enum, comme vous le feriez avec Java, par exemple:
enum SharedTPScopeManager: TPScopeManager {
case Singleton
}
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
}()
}
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:
À 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()
}
}
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.
}
Je préfère cette implémentation:
class APIClient {
}
var sharedAPIClient: APIClient = {
return APIClient()
}()
extension APIClient {
class func sharedClient() -> APIClient {
return sharedAPIClient
}
}
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
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()
}
}
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!
}
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