dispatch_once po zmianach interfejsu Swift 3 GCD API

84

Jaka jest nowa składnia dispatch_oncew Swift po zmianach wprowadzonych w wersji językowej 3? Stara wersja wyglądała następująco.

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

Oto zmiany wprowadzone w libdispatch .

David
źródło
Możliwy duplikat: dokąd dispatch_once w Swift 3?
rickster
Na podstawie odpowiedzi stackoverflow.com/a/38311178/1648724 i stackoverflow.com/a/39983813/1648724 stworzyłem CocoaPod, aby to zrobić: pod 'SwiftDispatchOnce', '~> 1.0'Pozdrawiam. :]
JRG-Developer,

Odpowiedzi:

70

Z dokumentu :

Wysyłka
Bezpłatna funkcja dispatch_once nie jest już dostępna w Swift. W Swift możesz leniwie inicjować globale lub statyczne właściwości i uzyskać takie same gwarancje bezpieczeństwa wątków i jednorazowego wywołania, jak podano w dispatch_once. Przykład:

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
źródło
3
To nie jest tak, jakbyś nie wiedział, że Swift będzie się szybko zmieniać i będziesz musiał poprawić wiele zepsutego kodu między wersjami Swift.
Abizern
2
największym problemem są strąki innych firm, które nie zawsze są kompatybilne ze Swift3.
Tinkerbell
4
To jest dług techniczny, który narastasz, wprowadzając zależności od stron trzecich, @Tinkerbell. Uwielbiam Swifta, ale jestem bardzo ostrożny, wprowadzając zewnętrzne zależności, które używają go właśnie z tego powodu.
Chris Wagner,
17
dispatch_oncebył czysty. To niestety jest brzydkie i zagmatwane…
Alexandre G,
104

Chociaż używanie leniwych zainicjowanych globali może mieć sens w przypadku jednorazowej inicjalizacji, nie ma sensu w przypadku innych typów. Używanie leniwych zainicjowanych globali do rzeczy takich jak singletony ma sens, nie ma to większego sensu w przypadku rzeczy takich jak ochrona konfiguracji swizzle.

Oto implementacja metody dispatch_once w stylu Swift 3:

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

Oto przykład użycia:

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

lub używając UUID

private let _onceToken = NSUUID().uuidString

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

Ponieważ jesteśmy obecnie w okresie przechodzenia od swift 2 do 3, oto przykład implementacji 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
źródło
Bardzo dziękuję za rozwiązanie. Byłem dokładnie uwięziony w zawirowaniach. Mam nadzieję, że szybki zespół zajmie się tym przypadkiem użycia.
salman140
2
Absolutnie nie powinieneś już używać objc_sync_enteri objc_sync_exitwięcej.
smat88dd
1
A czemu to?
Tod Cunningham
1
Aby uzyskać wydajność, należy użyć zestawu zamiast tablicy dla _onceTrackers. Poprawia to złożoność czasową od O (N) do O (1).
Werner Altewischer
2
Więc piszesz klasę wielokrotnego użytku, zakładając, że nie będzie ona tak często używana :-) Jeśli nie wymaga to dodatkowego wysiłku, aby zmniejszyć złożoność czasową z O (N) do O (1), zawsze powinieneś to zrobić IMHO.
Werner Altewischer
62

Rozszerzając powyższą odpowiedź Toda Cunninghama, dodałem inną metodę, która automatycznie tworzy token z pliku, funkcji i wiersza.

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

Więc może być prościej zadzwonić:

DispatchQueue.once {
    setupUI()
}

i nadal możesz określić token, jeśli chcesz:

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

Przypuszczam, że możesz mieć kolizję, jeśli masz ten sam plik w dwóch modułach. Szkoda, że ​​nie ma#module

VaporwareWolf
źródło
to rzuca więcej światła na to, co się dzieje. Dzięki.
nyxee
Link do powyższego kodu: gitlab.com/zakkhoyt/DispatchOnceExample/raw/master/…
VaporwareWolf
Naprawdę pomogło Thanx
Manjunath C.Kadani
19

Edytować

Odpowiedź @ Frizlab - nie ma gwarancji, że to rozwiązanie będzie bezpieczne dla wątków. Jeśli ma to kluczowe znaczenie, należy skorzystać z alternatywy

Proste rozwiązanie to

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

    self.title = "Hello Lazy Guy"

    return
}()

używany jak

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    _ = dispatchOnce
}
Ryan Heitner
źródło
1
To w ogóle nie pomaga, ponieważ leniwej deklaracji var nie można wprowadzić bezpośrednio w zwykłym kodzie, musi ona znajdować się w definicji struktury lub klasy. Oznacza to, że zawartość dispatchOnce nie może przechwycić otaczającego zakresu wystąpienia. Na przykład, jeśli zadeklarujesz zamknięcie, które jeszcze się nie uruchomiło, nie możesz następnie zadeklarować struktury wewnątrz tego zamknięcia i
sprawić,
3
Ocena negatywna, ponieważ ten kod zdecydowanie nie ma takiej samej semantyki jak dispatch_once. dispatch_once zapewnia, że ​​kod zostanie uruchomiony dokładnie raz, niezależnie od wątku, z którego go wywołasz . Leniwe zmienne mają niezdefiniowane zachowanie w środowisku wielowątkowym.
Frizlab
w tym rozwiązaniu blok init zadzwoni dwa razy w niektórych przypadkach
święty
8

Nadal możesz go używać, jeśli dodasz nagłówek mostkujący:

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

Następnie .mgdzieś:

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

Powinieneś teraz móc używać mxcl_dispatch_oncez poziomu Swift.

Przede wszystkim powinieneś użyć tego, co sugeruje Apple, ale miałem kilka uzasadnionych zastosowań, w których musiałem, dispatch_oncez jednym tokenem w dwóch funkcjach, a zamiast tego nie jest to objęte tym, co zapewnia Apple.

mxcl
źródło
7

Możesz zadeklarować funkcję zmiennej najwyższego poziomu w następujący sposób:

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

następnie zadzwoń do tego gdziekolwiek:

doOnce()
Bogdan Novikov
źródło
1
Leniwe zmienne są ograniczone do klasy, więc to absolutnie nie będzie działać jak dispatch_once. Będzie wykonywany raz na wystąpienie klasy bazowej. Przenieś ją poza klasę [private var doOnce: () -> () = {}] lub oznacz jako statyczną [statyczna prywatna zmienna doOnce: () -> () = {}]
Eli Burke
1
Absolutnie poprawne! Dzięki. W większości przypadków wystarczy jedno działanie na instancję.
Bogdan Novikov
2
To naprawdę świetne rozwiązanie! Elegancki, krótki i wyraźny
Ben Leggiero
6

Swift 3: Dla tych, którzy lubią klasy (lub struktury) wielokrotnego użytku:

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

Stosowanie:

class MyViewController: UIViewController {

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

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

}

Aktualizacja (28 kwietnia 2017 r.): OSSpinLockZastąpiono os_unfair_lockostrzeżeniami o stosownym wycofaniu w zestawie 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
źródło
Otrzymuję komunikat, że OSSSpinLock jest przestarzały w iOS 10.0
markhorrocks
2
Dzięki! Zaktualizowano przykładowy kod. OSSpinLockzastąpiony os_unfair_lock. BTW: Oto dobry film WWDC o Concurrent Programming: developer.apple.com/videos/play/wwdc2016/720
Vlad
0

Poprawiam powyższe odpowiedzi otrzymuję wynik:

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
źródło
-3

Użyj metody stałej klasy, jeśli używasz Swift 1.2 lub nowszego, a zagnieżdżonej struktury, jeśli potrzebujesz obsługiwać wcześniejsze wersje. Eksploracja wzoru Singleton w Swift. Wszystkie poniższe podejścia obsługują leniwą inicjalizację i bezpieczeństwo wątków. Podejście dispatch_once nie działa w Swift 3.0

Podejście A: stała klasy

class SingletonA {

    static let sharedInstance = SingletonA()

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

}

Podejście B: struktura zagnieżdżona

class SingletonB {

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

}

Podejście 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
źródło
1
Pytanie dotyczyło konkretnie rozwiązania dla Swift 3.
thesummersign