2015-12-10 11 views
10

Die Antworten, die ich bisher gesehen habe (1, 2, 3) empfehlen GCD die mit dispatch_once so:Wie führe ich Code einmal und nur einmal in Swift aus?

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

Ausgang:

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

Aber warten Sie eine Minute. token ist eine Variable, so konnte ich dies ganz einfach:

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

Ausgang:

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

So dispatch_once ist nichts, wenn wir ich den Wert von token ändern können! Und token in eine Konstante zu verwandeln ist nicht einfach, da es vom Typ braucht.

Also sollten wir aufgeben auf dispatch_once in Swift? Gibt es einen sichereren Weg, Code nur einmal auszuführen?

+0

Objective-C hat das gleiche Problem. Die Idee ist, das "Token" in den gleichen Umfang wie den 'dispatch_once' Block zu stellen (und ihm einen besseren Namen wie' onceToken' zu geben und es RECHTS über den 'dispatch_once' Block selbst zu stellen, so dass es sehr klar ist). – nhgrif

+0

Nun, 'dispatch_once' ist nicht sicherer als eine normale boolesche Variable. – Eric

+0

http://Stackoverflow.com/q/25354882/2792531 – nhgrif

Antwort

13

Statische Eigenschaften durch einen Verschluss initialisiert werden träge und höchstens einmal, so dass diese Drucke nur einmal ausgeführt, in

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

Beispiel läuft:

~/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. 
+0

Das funktioniert solide. Es ist nur ein bisschen umständlich, es in einen Type zu packen, aber das entspricht einer Menge von Anwendungen. – Eric

+3

Sie müssen keine statische Eigenschaft in einen Typ umbrechen. Sie können eine globale Variable oder Konstante deklarieren und sie mit einem Closure initialisieren. Der Verschluss wird einmal träge genannt. Die Ausnahme ist, dass, wenn die globale Variable/Konstante in main.swift definiert ist, sie immer noch einmal ausgeführt wird, aber in der Reihenfolge der Definition (d. H. Nicht träge). –

+1

Nebenbei verwendet dies tatsächlich 'dispatch_once' im generierten Code :). Es versteckt es nur in der Sprachsemantik. –

17

Ein Mann ging zum Arzt und sagte: "Doktor, es tut weh, wenn ich auf meinen Fuß stampfe". Der Arzt antwortete: "Also hör auf damit."

Wenn Sie Ihr Versand-Token absichtlich ändern, dann ja - Sie können den Code zweimal ausführen. Aber wenn Sie umgehen die Logik entwickelt, um mehrere Ausführungen in Weise zu verhindern, können Sie es tun. dispatch_once ist immer noch die beste Methode, um sicherzustellen, dass Code nur einmal ausgeführt wird, da er alle (sehr) komplexen Fälle um Initialisierungs- und Racebedingungen behandelt, die ein einfacher Boolean nicht abdecken wird.

Wenn Sie befürchten, dass jemand das Token versehentlich zurücksetzen könnte, können Sie es in eine Methode einpacken und es so offensichtlich wie möglich machen, was die Konsequenzen sind. So etwas wie die folgenden Willen Umfang der Token dem Verfahren, und verhindern, dass jemand es ohne ernsthafte Anstrengung zu ändern:

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

    dispatch_once(&TokenContainer.token) { 
     print("This is printed only on the first call") 
    } 
} 
+2

Erste Zeile allein war wahrscheinlich alles, was Sie brauchten. Der Rest der Antwort ist überflüssig. ;) – nhgrif

+0

LOL. Beste Erklärung. –

+0

Ich stimme grundsätzlich nicht zu. Die Dokumentation für "dispatch_once" besagt, dass es "ein Blockobjekt einmal und nur einmal für die Lebensdauer einer Anwendung ausführt". Keine Zweideutigkeit hier, die API bricht den Vertrag. Und dieser Arzt ist inkompetent. – Eric

2

ich denke nur Ressourcen der beste Ansatz lazily Konstrukt, wie es benötigt wird, um trotz zweimal aufgerufen wird. Swift macht das einfach.

Es gibt mehrere Optionen. Wie bereits erwähnt, können Sie eine statische Eigenschaft innerhalb eines Typs mit einem Closure initialisieren.

jedoch die einfachste Möglichkeit ist, eine globale Variable (oder Konstante) zu definieren und es mit einem Verschluss initialisieren dann diese Variable verweisen überall die Initialisierung Code ist erforderlich, einmal geschehen sein:

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

Eine weitere Option ist Um den Typ innerhalb einer Funktion zu wickeln, so dass es beim Aufruf besser liest.Zum Beispiel:

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

    let _ = Resource.resourceInit 
} 

Sie können bei Bedarf Variationen vornehmen. Anstatt beispielsweise den internen Typ der Funktion zu verwenden, können Sie je nach Bedarf eine private globale und interne oder öffentliche Funktion verwenden.

Ich denke jedoch, der beste Ansatz ist nur festzustellen, welche Ressourcen Sie benötigen, um sie als globale oder statische Eigenschaften zu initialisieren und zu erstellen.

Verwandte Themen