Où dispatch_once dans Swift 3?

Bon, j’ai découvert la nouvelle API Swifty Dispatch dans Xcode 8. Je m’amuse avec DispatchQueue.main.async , et j’ai parcouru le module Dispatch dans Xcode pour trouver toutes les nouvelles API.

Mais j’utilise également dispatch_once pour m’assurer que des choses comme la création de singleton et la configuration unique ne sont pas exécutées plus d’une fois (même dans un environnement multithread) … et que dispatch_once est introuvable dans le nouveau module Dispatch?

 static var token: dispatch_once_t = 0 func whatDoYouHear() { print("All of this has happened before, and all of it will happen again.") dispatch_once(&token) { print("Except this part.") } } 

Depuis Swift 1.x, Swift utilise dispatch_once arrière-plan pour effectuer une initialisation paresseuse sûre des threads des propriétés globales et statiques.

Donc, la static var ci-dessus utilisait déjà dispatch_once , ce qui la rend un peu étrange (et peut-être problématique de l’utiliser à nouveau comme jeton pour un autre dispatch_once . En fait, il n’existe aucun moyen sûr d’utiliser dispatch_once sans ce type de récursivité). au lieu de cela, il suffit d’utiliser les fonctionnalités de langage construites sur elle:

 // global constant: SomeClass initializer gets called lazily, only on first use let foo = SomeClass() // global var, same thing happens here // even though the "initializer" is an immediately invoked closure var bar: SomeClass = { let b = SomeClass() b.someProperty = "whatever" b.doSomeStuff() return b }() // ditto for static properties in classes/structures/enums class MyClass { static let singleton = MyClass() init() { print("foo") } } 

Si vous avez utilisé dispatch_once pour une initialisation unique qui donne une certaine valeur, vous pouvez simplement rendre cette valeur la variable globale ou la propriété statique que vous initialisez.

Mais que se passe-t-il si vous utilisez dispatch_once pour faire un travail qui n’a pas nécessairement de résultat? Vous pouvez toujours le faire avec une variable globale ou une propriété statique: créez simplement le type Void cette variable:

 let justAOneTimeThing: () = { print("Not coming back here.") }() 

Et si accéder à une variable globale ou à une propriété statique pour effectuer un travail ponctuel ne vous semble pas juste, par exemple, vous voulez que vos clients appellent une fonction “initialiser moi” avant de travailler avec votre bibliothèque. access dans une fonction:

 func doTheOneTimeThing() { justAOneTimeThing } 

Voir le guide de migration pour plus d’informations.

Les autres réponses ici et autour des interwebs sont plutôt bonnes, mais je pense que cette petite information devrait également être mentionnée:

L’avantage de dispatch_once était qu’il était optimisé, essentiellement en corrigeant le code après la première exécution d’une manière que je comprenais à peine, mais je suis raisonnablement assuré que cela serait beaucoup plus rapide que de définir et de vérifier un jeton global.

Bien que le jeton puisse être raisonnablement implémenté dans Swift, le fait de devoir déclarer une autre valeur booléenne stockée n’est pas génial. Sans parler de thread-safe. Comme le dit le document , vous devriez utiliser un “global paresseux initialisé”. Ouais, mais pourquoi encombrer la scope mondiale, non?

Jusqu’à ce que quelqu’un me convainque d’une meilleure méthode, j’ai tendance à déclarer ma fermeture unique dans le périmètre que je vais utiliser ou raisonnablement près de la suivante:

 private lazy var foo: Void = { // Do this once }() 

Fondamentalement, je dis que “quand je lis ceci, foo devrait être le résultat de l’exécution de ce bloc.” Il se comporte exactement de la même manière qu’une constante de let globale, juste dans la bonne scope. Et plus joli Ensuite, je l’appellerais n’importe où, en le lisant dans quelque chose qui ne sera jamais utilisé autrement. J’aime celle de Swift pour ça. Ainsi:

 _ = foo 

Cette bizarrerie vraiment cool a eu lieu il y a un certain temps, mais n’a pas vu beaucoup d’amour. En gros, elle laisse la variable seule à l’exécution, comme une fermeture non appelée, jusqu’à ce que quelque chose veuille voir son résultat Void . En lecture, il appelle la fermeture, le jette et garde son résultat dans foo . Void n’utilise pratiquement rien en mémoire, donc les lectures suivantes (ie _ = foo ) ne font rien sur le CPU. (Ne me citez pas à ce sujet, quelqu’un s’il vous plaît vérifier sur l’assemblée pour en être sûr!) Avoir autant que vous le souhaitez, et Swift quitte essentiellement s’en soucier après la première manche! Perdez ce vieux dispatch_once_t et gardez beaucoup de code aussi joli que lorsque vous l’avez ouvert pour la première fois le jour de Noël!

Mon seul problème est que vous pouvez définir foo sur autre chose avant sa première lecture, puis votre code ne sera jamais appelé! D’où la constante globale let qui empêche cela. En fait, les constantes dans la scope de la classe ne jouent pas bien avec selfself , donc ne jouez pas avec les variables d’instance … Mais sérieusement, quand définissez-vous n’importe quoi pour Void ?

Cela, et vous devez spécifier le type de retour comme Void ou () , sinon il se plaindra toujours de self . Who’da Thunk?

Et lazy est juste pour rendre la variable aussi paresseuse que cela devrait être, donc Swift ne l’exécute pas directement sur init() .

Assez chic, à condition de ne pas y écrire! : P

Exemple pour “dispatch_once” dans Swift 3.0

Étape 1: Remplacez juste le code ci-dessous par votre Singleton.swift (classe Singleton)

 // Singleton Class class Singleton: NSObject { var strSample = NSSsortingng() static let sharedInstance:Singleton = { let instance = Singleton () return instance } () // MARK: Init override init() { print("My Class Initialized") // initialized with variable or property strSample = "My Ssortingng" } } 

Singleton Sample Image

Étape 2: Appelez Singleton à partir de ViewController.swift

 // ViewController.swift override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. let mySingleton = Singleton.sharedInstance print(mySingleton.strSample) mySingleton.strSample = "New Ssortingng" print(mySingleton.strSample) let mySingleton1 = Singleton.sharedInstance print(mySingleton1.strSample) } 

Image échantillon de ViewController

Sortie comme ça

 My Class Initialized My Ssortingng New Ssortingng New Ssortingng 

Comstack sous Xcode 8 GA Swift 3

La manière élégante et recommandée de créer une instance de classe singleton dispatch_once:

 final class TheRoot { static let shared = TheRoot() var appState : AppState = .normal ... 

Pour l’utiliser:

 if TheRoot.shared.appState == .normal { ... } 

Que font ces lignes?

final – donc la classe ne peut pas être remplacée, étendue, cela rend aussi le code un peu plus rapide à exécuter, moins les indirections.

static let shared = TheRoot () – Cette ligne effectue un init paresseux et ne s’exécute qu’une seule fois.

Cette solution est sécurisée pour les threads.

Bien que le modèle “lazy var” me permette de ne plus se soucier des jetons de répartition et qu’il soit généralement plus pratique que dispatch_once() , je n’aime pas son aspect sur le site d’appel:

 _ = doSomethingOnce 

Je m’attendrais à ce que cette déclaration ressemble plus à un appel de fonction (car cela implique une action), mais cela ne semble pas du tout le cas. De plus, devoir écrire _ = pour éliminer explicitement le résultat est inutile et ennuyeux.

Il y a un meilleur moyen:

 lazy var doSomethingOnce: () -> Void = { print("executed once") return {} }() 

Ce qui rend les suivantes possibles:

 doSomethingOnce() 

Cela pourrait être moins efficace (car il appelle une fermeture vide au lieu de simplement supprimer un Void ), mais une meilleure clarté en vaut la peine pour moi.

Selon le guide de migration :

La fonction gratuite dispatch_once n’est plus disponible dans Swift. Dans Swift, vous pouvez utiliser des globaux ou des propriétés statiques initialisés paresseusement et obtenir les mêmes garanties de sécurité des threads et appelées une fois que celles fournies par dispatch_once.

Exemple:

  let myGlobal = { … global contains initialization in a call to a closure … }() // using myGlobal will invoke the initialization code only the first time it is used. _ = myGlobal 

Thread-safe dispatch_once:

 private lazy var foo: Void = { objc_sync_enter(self) defer { objc_sync_exit(self) } // Do this once }()