web-dev-qa-db-fra.com

dispatch_once après les Swift 3 modifications de l'API GCD

Quelle est la nouvelle syntaxe pour dispatch_once dans Swift après les modifications apportées à la version 3 du langage? L'ancienne version était la suivante.

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

Ces sont les modifications apportées à libdispatch qui ont été faites.

75
David

De la doc :

Envoi
La fonction gratuite dispatch_once n'est plus disponible dans Swift. Dans Swift, vous pouvez utiliser des propriétés globales ou statiques initialisées paresseusement et obtenir les mêmes garanties de sécurité de thread et appelées qu'une fois fournies par dispatch_once. Exemple:

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.
55
Dershowitz123

L'utilisation de globales initialisées paresseuses peut avoir un sens pour une initialisation unique, mais pas pour d'autres types. Il est très logique d'utiliser des globales initialisées paresseux pour des éléments tels que des singletons, mais pas pour des éléments tels que la protection d'une configuration de type "swizzle".

Voici une implémentation de style Swift 3 de 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()
    }
}

Voici un exemple d'utilisation:

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

ou en utilisant un UUID

private let _onceToken = NSUUID().uuidString

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

Comme nous sommes actuellement dans une période de transition de Swift 2 à 3, voici un exemple de mise en oeuvre de 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()
    }

}
92
Tod Cunningham

En développant la réponse de Tod Cunningham ci-dessus, j'ai ajouté une autre méthode qui rend le jeton automatiquement à partir d'un fichier, d'une fonction et d'une ligne.

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

Donc, il peut être plus simple d'appeler:

DispatchQueue.once {
    setupUI()
}

et vous pouvez toujours spécifier un jeton si vous le souhaitez:

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

Je suppose que vous pourriez avoir une collision si vous avez le même fichier dans deux modules. Dommage qu'il n'y ait pas #module

56
VaporwareWolf

Modifier

Réponse de @ Frizlab - cette solution n'est pas garantie en termes de threads. Une alternative doit être utilisée si cela est crucial

La solution simple est

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

    self.title = "Hello Lazy Guy"

    return
}()

utilisé comme

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    _ = dispatchOnce
}
17
Ryan Heitner

Vous pouvez toujours l'utiliser si vous ajoutez un en-tête de pontage:

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

Puis dans un .m quelque part:

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

Vous devriez maintenant pouvoir utiliser mxcl_dispatch_once de Swift.

La plupart du temps, vous devriez utiliser ce que Apple suggère à la place, mais j'avais des utilisations légitimes pour lesquelles j'avais besoin de dispatch_once avec un seul jeton dans deux fonctions et il n'est pas couvert par ce que Apple fournir à la place.

8
mxcl

Swift 3: Pour ceux qui aiment les classes (ou structures) réutilisables:

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

Usage:

class MyViewController: UIViewController {

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

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

}

Mise à jour (28 avril 2017): OSSpinLock remplacé par os_unfair_lock avertissements de dépréciation dus dans 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)
   }
}
5
Vlad

Vous pouvez déclarer une fonction de variable de niveau supérieur comme ceci:

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

puis appelez cela n'importe où:

doOnce()
4
Bogdan Novikov