dispatch_once nach Änderungen der Swift 3 GCD-API

81

Was ist die neue Syntax dispatch_oncein Swift nach den Änderungen in Sprachversion 3? Die alte Version war wie folgt.

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

Dies sind die Änderungen an libdispatch , die vorgenommen wurden.

David
quelle
Mögliches Duplikat: Wohin dispatch_once in Swift 3?
Rickster
Basierend auf den Antworten stackoverflow.com/a/38311178/1648724 und stackoverflow.com/a/39983813/1648724 habe ich einen CocoaPod erstellt, um dies zu tun: pod 'SwiftDispatchOnce', '~> 1.0'Prost. :]
JRG-Entwickler

Antworten:

65

Aus dem Dokument :

Versand
Die kostenlose Funktion dispatch_once ist in Swift nicht mehr verfügbar. In Swift können Sie träge initialisierte Globals oder statische Eigenschaften verwenden und erhalten die gleichen Thread-Sicherheits- und Call-Once-Garantien wie dispatch_once. 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.
Dershowitz123
quelle
3
Es ist nicht so, als ob Sie nicht wüssten, dass sich Swift schnell ändern würde und Sie zwischen den Versionen von Swift viel fehlerhaften Code korrigieren müssten.
Abizern
2
Der größte Schmerz sind die Pods von Drittanbietern, die nicht immer mit Swift3 kompatibel sind.
Tinkerbell
4
Das ist die technische Verschuldung, die Sie bei der Einführung von Abhängigkeiten von Drittanbietern, @Tinkerbell, anhäufen. Ich liebe Swift, bin aber besonders vorsichtig, wenn ich externe Abhängigkeiten einbringe, die es aus genau diesem Grund verwenden.
Chris Wagner
16
dispatch_oncewar klar. Dies ist leider hässlich und verwirrend ..
Alexandre G
102

Während die Verwendung von verzögert initialisierten Globals für eine einmalige Initialisierung sinnvoll sein kann, ist sie für andere Typen nicht sinnvoll. Es ist sehr sinnvoll, faul initialisierte Globals für Dinge wie Singletons zu verwenden. Es ist nicht sehr sinnvoll für Dinge wie das Bewachen eines Swizzle-Setups.

Hier ist eine Swift 3-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 Anwendungsbeispiel:

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

oder mit einer UUID

private let _onceToken = NSUUID().uuidString

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

Da wir uns derzeit in einer Zeit des Übergangs von Swift 2 zu 3 befinden, ist hier ein Beispiel für die Implementierung von Swift 2:

public class Dispatch
{
    private static var _onceTokenTracker = [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 token: String, @noescape block:dispatch_block_t) {
        objc_sync_enter(self); defer { objc_sync_exit(self) }

        if _onceTokenTracker.contains(token) {
            return
        }

        _onceTokenTracker.append(token)
        block()
    }

}
Tod Cunningham
quelle
Vielen Dank für die Lösung. Ich war genau in einem Swizzle-Setup gefangen. Ich hoffe, das schnelle Team spricht diesen Anwendungsfall an.
Salman 140
2
Sie sollten absolut nicht mehr verwenden objc_sync_enterund objc_sync_exit.
smat88dd
1
Und warum ist das?
Tod Cunningham
1
Für die Leistung sollten Sie für die _onceTrackers einen Satz anstelle eines Arrays verwenden. Dies verbessert die zeitliche Komplexität von O (N) auf O (1).
Werner Altewischer
2
Sie schreiben also eine wiederverwendbare Klasse, vorausgesetzt, sie wird nicht so oft wiederverwendet :-) Wenn es keinen zusätzlichen Aufwand erfordert, die Zeitkomplexität von O (N) auf O (1) zu verringern, sollten Sie dies IMHO immer tun.
Werner Altewischer
62

Als Erweiterung der obigen Antwort von Tod Cunningham habe ich eine weitere Methode hinzugefügt, mit der das Token automatisch aus Datei, Funktion und Zeile erstellt wird.

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

    public class func once(file: String = #file,
                           function: String = #function,
                           line: Int = #line,
                           block: () -> Void) {
        let token = "\(file):\(function):\(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) {
        objc_sync_enter(self)
        defer { objc_sync_exit(self) }

        guard !_onceTracker.contains(token) else { return }

        _onceTracker.append(token)
        block()
    }
}

So kann es einfacher sein, Folgendes anzurufen:

DispatchQueue.once {
    setupUI()
}

und Sie können immer noch ein Token angeben, wenn Sie möchten:

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

Ich nehme an, Sie könnten eine Kollision bekommen, wenn Sie dieselbe Datei in zwei Modulen haben. Schade, dass es das nicht gibt#module

VaporwareWolf
quelle
Dies wirft etwas mehr Licht auf das, was los ist. Vielen Dank.
Nyxee
Link zum obigen Code: gitlab.com/zakkhoyt/DispatchOnceExample/raw/master/…
VaporwareWolf
Wirklich geholfen Thanx
Manjunath C.Kadani
18

Bearbeiten

Antwort von @ Frizlab - Diese Lösung ist nicht garantiert threadsicher. Eine Alternative sollte verwendet werden, wenn dies entscheidend ist

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
}
Ryan Heitner
quelle
1
Dies hilft überhaupt nicht, da eine Lazy-Var-Deklaration nicht mit regulärem Code inline erstellt werden kann, sondern in einer Struktur- oder Klassendefinition vorliegen muss. Das bedeutet, dass der Inhalt von dispatchOnce den umgebenden Bereich einer Instanz nicht erfassen kann. Wenn Sie beispielsweise einen Abschluss deklarieren, der noch nicht ausgeführt wurde, können Sie die Struktur in diesem Abschluss nicht deklarieren und den Inhalt des faulen
Vars
3
Downvoted, da dieser Code definitiv nicht die gleiche Semantik wie dispatch_once hat. dispatch_once stellt sicher, dass der Code genau einmal ausgeführt wird, unabhängig davon, von welchem ​​Thread Sie ihn aufrufen . Lazy Vars haben ein undefiniertes Verhalten in einer Multithread-Umgebung.
Frizlab
In dieser Lösung wird der Init-Block in einigen Fällen zweimal aufgerufen
heilig
8

Sie können es weiterhin verwenden, wenn Sie einen Bridging-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 .mirgendwo:

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

Sie sollten jetzt in der Lage sein, mxcl_dispatch_oncevon Swift zu verwenden.

Meistens sollten Sie stattdessen das verwenden, was Apple vorschlägt, aber ich hatte einige legitime Verwendungszwecke, bei denen ich dispatch_oncemit einem einzigen Token in zwei Funktionen arbeiten musste, und es wird nicht abgedeckt, was Apple stattdessen bereitstellt.

mxcl
quelle
7

Sie können eine Variablenfunktion der obersten Ebene wie folgt deklarieren:

private var doOnce: ()->() = {
    /* do some work only once per instance */
    return {}
}()

dann nenne das irgendwo:

doOnce()
Bogdan Novikov
quelle
1
Lazy Vars sind auf die Klasse beschränkt, daher verhält sich dies absolut nicht wie dispatch_once. Es wird einmal pro Instanz der zugrunde liegenden Klasse ausgeführt. Verschieben Sie es entweder außerhalb der Klasse [private var doOnce: () -> () = {}] oder markieren Sie es als statisch [statische private var doOnce: () -> () = {}]
Eli Burke
1
Absolut korrekt! Vielen Dank. In den meisten Fällen benötigen Sie eine Aktion pro Instanz.
Bogdan Novikov
2
Dies ist eine wirklich gute Lösung! Elegant, kurz und klar
Ben Leggiero
6

Swift 3: Für diejenigen, die wiederverwendbare Klassen (oder Strukturen) mögen:

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

Verwendung:

class MyViewController: UIViewController {

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

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

}

Update (28. April 2017): OSSpinLockErsetzt durch os_unfair_lockfällige Verfallswarnungen 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)
   }
}
Vlad
quelle
Ich erhalte die Meldung, dass OSSSpinLock in iOS 10.0
Markhorrocks am
2
Vielen Dank! Beispielcode aktualisiert. OSSpinLockersetzt durch os_unfair_lock. Übrigens: Hier ist ein gutes WWDC-Video über Concurrent Programming: developer.apple.com/videos/play/wwdc2016/720
Vlad
0

Ich verbessere die obigen Antworten und erhalte das Ergebnis:

import Foundation
extension DispatchQueue {
    private static var _onceTracker = [AnyHashable]()

    ///only excute once in same file&&func&&line
    public class func onceInLocation(file: String = #file,
                           function: String = #function,
                           line: Int = #line,
                           block: () -> Void) {
        let token = "\(file):\(function):\(line)"
        once(token: token, block: block)
    }

    ///only excute once in same Variable
    public class func onceInVariable(variable:NSObject, block: () -> Void){
        once(token: variable.rawPointer, 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: AnyHashable,block: () -> Void) {
        objc_sync_enter(self)
        defer { objc_sync_exit(self) }

        guard !_onceTracker.contains(token) else { return }

        _onceTracker.append(token)
        block()
    }

}

extension NSObject {
    public var rawPointer:UnsafeMutableRawPointer? {
        get {
            Unmanaged.passUnretained(self).toOpaque()
        }
    }
}
leo.tan
quelle
-3

Verwenden Sie den Klassenkonstantenansatz, wenn Sie Swift 1.2 oder höher verwenden, und den verschachtelten Strukturansatz, wenn Sie frühere Versionen unterstützen müssen. Eine Untersuchung des Singleton-Musters in Swift. Alle folgenden Ansätze unterstützen eine verzögerte Initialisierung und Thread-Sicherheit. Der dispatch_once-Ansatz funktioniert in Swift 3.0 nicht

Ansatz A: Klassenkonstante

class SingletonA {

    static let sharedInstance = SingletonA()

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

}

Ansatz B: Verschachtelte Struktur

class SingletonB {

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

}

Ansatz C: dispatch_once

class SingletonC {

    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!
    }
}
Shan Shafiq
quelle
1
Die Frage speziell nach einer Lösung für Swift 3.
Thesummersign