2016-06-17 5 views
56

Was ist die neue Syntax für dispatch_once in Swift nach den Änderungen in der Sprachversion 3 gemacht? Die alte Version war wie folgt.dispatch_once nach der Swift 3 GCD API Änderungen

var token: dispatch_once_t = 0 
func test() { 
    dispatch_once(&token) { 
    } 
} 

Diese are the changes to libdispatch wurden gemacht.

+0

Mögliche doppelte: [Whither disp atch_once in Swift 3?] (http://stackoverflow.com/q/37801407/957768) – rickster

+0

Basierend auf den Antworten https://stackoverflow.com/a/38311178/1648724 und https://stackoverflow.com/a/ 39983813/1648724, ich habe dafür einen CocoaPod erstellt: ['pod 'SwiftDispatchOnce', '~> 1.0''] (https://github.com/JRG-Developer/SwiftDispatchOnce) Cheers. :] –

Antwort

38

Vom doc:

Versand
Die freie Funktion dispatch_once ist nicht mehr verfügbar in Swift. In Swift können Sie träge initialisierte Globals oder statische Properties verwenden und erhalten die gleichen Thread-Sicherheit und Einmal-Once-Garantien als dispatch_once zur Verfügung gestellt. Beispiel:

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

Es ist nicht so, als wüsstest du nicht, dass sich Swift schnell ändern würde und du zwischen den Versionen von Swift viel kaputten Code korrigieren müsstest. – Abizern

+2

der größte Schmerz ist die 3rd Party Pods, die nicht immer Swift3 kompatibel sind. – Tinkerbell

+4

Das sind die technischen Schulden, die bei der Einführung von Abhängigkeiten von Drittanbietern, @Tinkerbell, anfallen. Ich liebe Swift, bin aber besonders vorsichtig, externe Abhängigkeiten einzubringen, die es aus diesem Grund nutzen. –

72

Während faul initialisiert Globals mit Sinn für einige einmalige Initialisierung machen, macht es keinen Sinn für andere Arten machen. Es macht viel Sinn, faul initialisierte Globals für Dinge wie Singletons zu verwenden, es macht nicht viel Sinn für Dinge wie die Überwachung eines Swizzle-Setups.

Hier ist ein Swift 3 Stil Implementierung von dispatch_once:

public extension DispatchQueue { 

    private static var _onceTracker = [String]() 

    /** 
    Executes a block of code, associated with a unique token, only once. The code is thread safe and will 
    only execute the code once even in the presence of multithreaded calls. 

    - parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID 
    - parameter block: Block to execute once 
    */ 
    public class func once(token: String, block:@noescape(Void)->Void) { 
     objc_sync_enter(self); defer { objc_sync_exit(self) } 

     if _onceTracker.contains(token) { 
      return 
     } 

     _onceTracker.append(token) 
     block() 
    } 
} 

Hier ist ein Beispiel für die Verwendung:

DispatchQueue.once(token: "com.vectorform.test") { 
    print("Do This Once!") 
} 

oder mit einem UUID

private let _onceToken = NSUUID().uuidString 

DispatchQueue.once(token: _onceToken) { 
    print("Do This Once!") 
} 

Da wir zur Zeit sind in eine Zeit des Übergangs von Swift 2 zu 3, hier ist ein Beispiel Swift 2 Implementierung:

+0

Vielen Dank für die Lösung. Ich wurde gerade in einem Swizzle-Setup gefangen. Ich hoffe, dass das schnelle Team diesen Anwendungsfall anspricht. – salman140

+0

Sie sollten "objc_sync_enter" und "objc_sync_exit" nicht mehr verwenden. – smat88dd

+0

Und warum ist das? –

35

Erweitern auf Tod Cunningham Antwort oben, habe ich eine andere Methode hinzugefügt, die das Token automatisch aus Datei, Funktion und Zeile macht.

public extension DispatchQueue { 
    private static var _onceTracker = [String]() 

    public class func once(file: String = #file, function: String = #function, line: Int = #line, block:(Void)->Void) { 
     let token = file + ":" + function + ":" + String(line) 
     once(token: token, block: block) 
    } 

    /** 
    Executes a block of code, associated with a unique token, only once. The code is thread safe and will 
    only execute the code once even in the presence of multithreaded calls. 

    - parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID 
    - parameter block: Block to execute once 
    */ 
    public class func once(token: String, block:(Void)->Void) { 
     objc_sync_enter(self) 
     defer { objc_sync_exit(self) } 


     if _onceTracker.contains(token) { 
      return 
     } 

     _onceTracker.append(token) 
     block() 
    } 
} 

So kann es einfacher sein zu nennen:

DispatchQueue.once { 
    setupUI() 
} 

und Sie können nach wie vor ein Token angeben, wenn Sie wollen:

DispatchQueue.once(token: "com.me.project") { 
    setupUI() 
} 

Ich nehme an, Sie eine Kollision bekommen könnte, wenn Sie die gleiche Datei in zwei Modulen. Schade, dass es nicht #module

+0

wirklich hilfreich danke – Svitlana

+0

dies wirft etwas mehr Licht auf was los ist. Vielen Dank. – nyxee

5

Sie können es immer noch verwenden, wenn Sie einen Überbrückungs Header hinzufügen:

typedef dispatch_once_t mxcl_dispatch_once_t; 
void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block); 

Dann wird in einem .m irgendwo:

void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block) { 
    dispatch_once(predicate, block); 
} 

Sie nun in der Lage sein sollte, verwenden mxcl_dispatch_once von Swift.

Meistens sollten Sie verwenden, was Apple stattdessen vorschlagen, aber ich hatte einige legitime Anwendungen, wo ich dispatch_once mit einem einzigen Token in zwei Funktionen brauchte, und es ist nicht durch das abgedeckt, was Apple stattdessen bietet.

-1

Verwenden Sie den Klassenkonstantenansatz, wenn Sie Swift 1.2 oder höher verwenden, und den geschachtelten Strukturansatz, wenn Sie frühere Versionen unterstützen müssen. Eine Untersuchung des Singleton-Musters in Swift. Alle folgenden Ansätze unterstützen die verzögerte Initialisierung und Thread-Sicherheit. Klasse Konstante Klasse SingletonA {

static let sharedInstance = SingletonA() 

init() { 
    println("AAA"); 
} 

} Ansatz B: Verschachtelte Struktur Klasse SingletonB {

class var sharedInstance: SingletonB { 
    struct Static { 
     static let instance: SingletonB = SingletonB() 
    } 
    return Static.instance 
} 

} Ansatz dispatch_once Ansatz wird in schnellen 3,0

Ansatz A nicht funktioniert C: dispatch_once Klasse Singleton {

class var sharedInstance: SingletonC { 
    struct Static { 
     static var onceToken: dispatch_once_t = 0 
     static var instance: SingletonC? = nil 
    } 
    dispatch_once(&Static.onceToken) { 
     Static.instance = SingletonC() 
    } 
    return Static.instance! 
} 

}

+0

Die Frage speziell nach einer Lösung für Swift 3 gefragt. – thesummersign

5

Swift 3: Für diejenigen, die gerne wiederverwendbare Klassen (oder Strukturen):

public final class /* struct */ DispatchOnce { 
    private var lock: OSSpinLock = OS_SPINLOCK_INIT 
    private var isInitialized = false 
    public /* mutating */ func perform(block: (Void) -> Void) { 
     OSSpinLockLock(&lock) 
     if !isInitialized { 
     block() 
     isInitialized = true 
     } 
     OSSpinLockUnlock(&lock) 
    } 
} 

Verbrauch:

class MyViewController: UIViewController { 

    private let /* var */ setUpOnce = DispatchOnce() 

    override func viewWillAppear() { 
     super.viewWillAppear() 
     setUpOnce.perform { 
     // Do some work here 
     // ... 
     } 
    } 

} 

aktualisieren (28. April 2017): OSSpinLock ersetzt durch os_unfair_lock fällige Verwarnungswarnungen in macOS SDK 10.12.

public final class /* struct */ DispatchOnce { 
    private var lock = os_unfair_lock() 
    private var isInitialized = false 
    public /* mutating */ func perform(block: (Void) -> Void) { 
     os_unfair_lock_lock(&lock) 
     if !isInitialized { 
     block() 
     isInitialized = true 
     } 
     os_unfair_lock_unlock(&lock) 
    } 
} 
+0

Ich bekomme eine Nachricht, dass OSSSpinLock in iOS 10.0 veraltet ist – markhorrocks

+2

Vielen Dank! Beispielcode aktualisiert 'OSSpinLock' ersetzt durch' os_unfair_lock'. BTW: Hier ist ein gutes WWDC-Video über 'Concurrent Programming': https://developer.apple.com/videos/play/wwdc2016/720/ – Vlad

8

Einfache Lösung ist

lazy var dispatchOnce : Void = { // or anyName I choose 

    self.title = "Hello Lazy Guy" 

    return 
}() 

verwendet wie

override func viewDidLayoutSubviews() { 
    super.viewDidLayoutSubviews() 
    _ = dispatchOnce 
} 
+0

Dies hilft überhaupt nicht, weil eine faule var-Deklaration nicht inline gemacht werden kann Bei regulärem Code muss es sich um eine Struktur- oder Klassendefinition handeln. Das bedeutet, dass der Inhalt von dispatchOnce den umgebenden Bereich einer Instanz nicht erfassen kann. Wenn Sie zum Beispiel eine Closure deklarieren, die noch nicht ausgeführt wurde, können Sie die Struktur innerhalb dieser Closure nicht deklarieren. Der Inhalt der Lazy-Variablen ist eine andere Clos, die Vars aus der umgebenden Closure erfasst. – CommaToast

+1

Downvoted, weil dieser Code definitiv ** hat nicht ** die gleiche Semantik wie dispatch_once. dispatch_once stellt sicher, dass der Code genau einmal ausgeführt wird **, unabhängig davon, von welchem ​​Thread er aufgerufen wird **. Lazy Vars haben ein undefiniertes Verhalten in einer Multithread-Umgebung. – Frizlab

-3
I have created below function 

func executeOnce(code: @escaping() -> Void) 
     { 
       if UserDefaults.standard.value(forKey: "3333##112233") == nil 
       { 
        code() 
        UserDefaults.standard.setValue("vv", forKey: "3333##112233") 
        UserDefaults.standard.synchronize() 
       } 
     } 

und Verwendung als unter

executeOnce { 

     print("onces") 
    }