web-dev-qa-db-fra.com

Où dispatch_once dans Swift 3?

D'accord, j'ai donc découvert le nouveau API de Swifty Dispatch dans Xcode 8. Je m'amuse à utiliser DispatchQueue.main.async, et j’ai parcouru le module Dispatch de Xcode pour trouver toutes les nouvelles API.

Mais j'utilise aussi dispatch_once pour s'assurer que des tâches telles que 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 dispatch_once ne figure nulle part 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.")
    }
}
49
rickster

Depuis Swift 1.x, Swift utilise dispatch_onceen coulisse pour effectuer une initialisation paresseuse sans risque de thread de variables globales et de propriétés statiques.

Alors le static var ci-dessus utilisait déjà dispatch_once, ce qui rend cela bizarre (et éventuellement problématique de l’utiliser à nouveau comme jeton pour un autre dispatch_once. En fait, il n'y a vraiment aucun moyen sûr d'utiliser dispatch_once sans ce genre de récursion, ils s'en sont donc débarrassés. Au lieu de cela, utilisez simplement les fonctionnalités du langage qui en découlent:

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

Donc, c’est génial si vous utilisez dispatch_once pour une initialisation unique qui donne une valeur - vous pouvez simplement faire de cette valeur la variable globale ou la propriété statique que vous initialisez.

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

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 tout simplement pas correct, par exemple, vous voulez que vos clients appellent une fonction "initialiser moi" avant de travailler avec votre bibliothèque - enroulez-le simplement accès dans une fonction:

func doTheOneTimeThing() {
    justAOneTimeThing
}

Voir le guide de migration pour plus d'informations.

54
rickster

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

La chose intéressante à propos de dispatch_once Est son optimisation: il mélange le code après la première utilisation d’une manière que je comprends à peine, mais je suis raisonnablement assuré que ce serait beaucoup plus rapide que de définir et de vérifier un (réel). jeton global.

Bien que le jeton puisse être raisonnablement implémenté dans Swift, devoir déclarer un autre booléen stocké n’est pas si terrible. Sans parler de thread-unsafe. Comme le doc dit, vous devriez utiliser un "global initialisé paresseux". Oui, mais pourquoi encombrer la portée mondiale, non?

Jusqu'à ce que quelqu'un me convainc d'une meilleure méthode, j'ai tendance à déclarer ma fermeture définitive dans le périmètre dans lequel je vais l'utiliser, ou raisonnablement proche de celle-ci, comme suit:

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

En gros, 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 globale let, dans la bonne portée. Et plus jolie. Ensuite, je l’appellerais où je le voudrais, en le lisant dans quelque chose qui ne sera jamais utilisé autrement. J'aime bien _ De Swift pour cela. Ainsi:

_ = foo

Cette bizarrerie vraiment cool existe depuis un certain temps, mais n'a pas vu beaucoup d'amour. Elle laisse essentiellement la variable seule à l'exécution, en tant que fermeture non appelée, jusqu'à ce que quelque chose veuille voir son résultat Void. En lecture, il appelle la fermeture, le jette et conserve son résultat dans foo. Void n'utilise pratiquement rien en mémoire, donc les lectures ultérieures (c'est-à-dire _ = foo) ne font rien sur la CPU. (Ne me citez pas dessus, s'il vous plaît, vérifiez s'il vous plaît sur l'Assemblée pour en être sûr!) En avez autant que vous voulez, et Swift arrête de s'en soucier après la première manche! Lose ce vieux dispatch_once_t, et conservez une grande partie de votre 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 quelque chose d'autre avant sa première lecture, puis votre code ne sera jamais ! D'où la constante globale let qui empêche cela. Le fait est que les constantes dans l'étendue de la classe ne fonctionnent pas bien avec self, donc pas de jeu avec les variables d'instance ... Mais sérieusement, quand définissez-vous quelque chose à Void de toute façon ??

Cela, et vous auriez besoin de spécifier le type de retour sous la forme Void ou (), Sinon il se plaindra toujours de self. Qui va thunk?

Et lazy n'a pour but que de rendre la variable aussi paresseuse qu'elle devrait l'être, donc Swift ne l'exécute pas directement sur init()].

Assez élégant, aussi longtemps que vous vous rappelez de ne pas écrire! : P

14
SeizeTheDay

Bien que le modèle "lazy var" me permette de ne plus me soucier des jetons d'expédition et qu'il soit généralement plus pratique que ne l'était dispatch_once(), je n'aime pas son aspect sur le site d'appel:

_ = doSomethingOnce

Je m'attendrais à ce que cette déclaration ressemble davantage à un appel de fonction (car elle implique une action), mais elle ne ressemble pas du tout. De plus, avoir à écrire _ = ignorer 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 possible ce qui suit:

doSomethingOnce()

Cela pourrait être moins efficace (puisqu'il appelle une fermeture vide au lieu de simplement jeter un Void), mais une clarté améliorée vaut totalement la peine pour moi.

13
Andrii Chernenko

Exemple pour "dispatch_once" in Swift 3.0

Étape 1: remplacez simplement le code ci-dessous par votre Singleton.Swift (classe Singleton)

// Singleton Class
class Singleton: NSObject { 
var strSample = NSString()

static let sharedInstance:Singleton = {
    let instance = Singleton ()
    return instance
} ()

// MARK: Init
 override init() {
    print("My Class Initialized")
    // initialized with variable or property
    strSample = "My String"
}
}

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

        print(mySingleton.strSample)

        let mySingleton1 = Singleton.sharedInstance
        print(mySingleton1.strSample)

    }

Image exemple ViewController

Sortie comme ça

My Class Initialized
My String
New String
New String
8
Vivek

Compile sous Xcode 8 GA Swift)

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

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

Pour l'utiliser:

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

Que font ces lignes?

final - pour que la classe ne puisse pas être surchargée, étendue, cela rend également le code un peu plus rapide à exécuter, moins d’indirections.

static let shared = TheRoot () - Cette ligne effectue une initialisation paresseuse et ne s'exécute qu'une fois.

Cette solution est thread-safe.

6
t1ser

Selon le Guide de migration :

La fonction gratuite dispatch_once n'est plus disponible dans Swift. Dans Swift, vous pouvez utiliser des propriétés globales ou statiques initialisées paresseusement et obtenir les mêmes garanties de sécurité de thread et appelées qu'une fois 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  
3
scollaco