web-dev-qa-db-fra.com

Comment exécuter du code une fois et une seule fois dans Swift?

Les réponses que j'ai vues jusqu'à présent ( 1 , 2 , ) recommandent d'utiliser le GCD dispatch_once Donc:

var token: dispatch_once_t = 0
func test() {
    dispatch_once(&token) {
        print("This is printed only on the first call to test()")
    }
    print("This is printed for each call to test()")
}
test()

Production:

This is printed only on the first call to test()
This is printed for each call to test()

Mais attendez une minute. token est une variable, donc je pourrais facilement faire ceci:

var token: dispatch_once_t = 0
func test() {
    dispatch_once(&token) {
        print("This is printed only on the first call to test()")
    }
    print("This is printed for each call to test()")
}
test()

token = 0

test()

Production:

This is printed only on the first call to test()
This is printed for each call to test()
This is printed only on the first call to test()
This is printed for each call to test()

Alors dispatch_once ne sert à rien si je peux changer la valeur de token! Et transformer token en une constante n'est pas simple car il doit être de type UnsafeMutablePointer<dispatch_once_t>.

Devrions-nous donc abandonner dispatch_once à Swift? Existe-t-il un moyen plus sûr d'exécuter du code une seule fois?

15
Eric

Les propriétés statiques initialisées par une fermeture sont exécutées paresseusement et au plus une fois, donc cela ne s'imprime qu'une seule fois, malgré un double appel:

/*
run like:

    Swift once.Swift
    Swift once.Swift run

to see both cases
*/
class Once {
    static let run: Void = {
        print("Behold! \(__FUNCTION__) runs!")
        return ()
    }()
}

if Process.arguments.indexOf("run") != nil {
    let _ = Once.run
    let _ = Once.run
    print("Called twice, but only printed \"Behold\" once, as desired.")
} else {
    print("Note how it's run lazily, so you won't see the \"Behold\" text now.")
}

L'exemple s'exécute:

~/W/WhenDoesStaticDefaultRun> Swift once.Swift
Note how it's run lazily, so you won't see the "Behold" text now.
~/W/WhenDoesStaticDefaultRun> Swift once.Swift run
Behold! Once runs!
Called twice, but only printed "Behold" once, as desired.
17
Jeremy W. Sherman

Un homme est allé chez le médecin et a dit "Docteur, ça fait mal quand je tape sur mon pied". Le médecin a répondu: "Alors arrêtez de le faire".

Si vous modifiez délibérément votre jeton d'expédition, alors oui - vous pourrez exécuter le code deux fois. Mais si vous contournez la logique conçue pour empêcher les exécutions multiples de la manière any, vous pourrez le faire. dispatch_once est toujours la meilleure méthode pour garantir que le code n'est exécuté qu'une seule fois, car il gère tous les cas de coin (très) complexes autour de l'initialisation et des conditions de concurrence qu'un simple booléen ne couvrira pas.

Si vous craignez que quelqu'un ne réinitialise accidentellement le jeton, vous pouvez l'intégrer dans une méthode et rendre aussi évidentes que les conséquences peuvent être. Quelque chose comme ce qui suit étendra le jeton à la méthode et empêchera quiconque de le modifier sans effort sérieux:

func willRunOnce() -> () {
    struct TokenContainer {
        static var token : dispatch_once_t = 0
    }

    dispatch_once(&TokenContainer.token) {
        print("This is printed only on the first call")
    }
}
21
Adam Wright

Je pense que la meilleure approche est de simplement construire des ressources paresseusement au besoin. Swift rend cela facile.

Il existe plusieurs options. Comme déjà mentionné, vous pouvez initialiser une propriété statique dans un type à l'aide d'une fermeture.

Cependant, l'option la plus simple consiste à définir une variable globale (ou constante) et à l'initialiser avec une fermeture, puis à référencer cette variable partout où le code d'initialisation doit se produire une fois:

let resourceInit : Void = {
  print("doing once...")
  // do something once
}()

Une autre option consiste à encapsuler le type dans une fonction afin qu'il se lise mieux lors de l'appel. Par exemple:

func doOnce() {
    struct Resource {
        static var resourceInit : Void = {
            print("doing something once...")
        }()
    }

    let _ = Resource.resourceInit
}

Vous pouvez faire des variations à ce sujet au besoin. Par exemple, au lieu d'utiliser le type interne à la fonction, vous pouvez utiliser une fonction privée globale et interne ou publique selon vos besoins.

Cependant, je pense que la meilleure approche consiste simplement à déterminer les ressources dont vous avez besoin pour initialiser et les créer paresseusement en tant que propriétés globales ou statiques.

2
Tom Pelaia