94 votes

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

Quelle est la nouvelle syntaxe pour dispatch_once en Swift après les changements apportés dans 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.

0 votes

0 votes

En fonction des réponses stackoverflow.com/a/38311178/1648724 y stackoverflow.com/a/39983813/1648724 J'ai donc créé un CocoaPod pour ce faire : pod 'SwiftDispatchOnce', '~> 1.0' Santé. :]

109voto

Tod Cunningham Points 702

Si l'utilisation de globaux initialisés paresseusement peut être judicieuse pour une initialisation ponctuelle, elle ne l'est pas pour d'autres types. L'utilisation de globaux initialisés paresseusement est très utile pour des choses comme les singletons, mais elle n'est pas très utile pour des choses comme la garde d'une configuration de swizzle.

Voici une implémentation de dispatch_once à la manière de 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()
    }
}

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 en période de transition de swift 2 à 3, voici un exemple de mise en œuvre 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()
    }

}

0 votes

Merci beaucoup pour la solution. J'étais exactement en train de me faire piéger dans une configuration de swizzle. J'espère que l'équipe swift se penchera sur ce cas d'utilisation.

2 votes

Vous ne devez absolument pas utiliser objc_sync_enter y objc_sync_exit plus.

1 votes

Pourquoi ?

77voto

Dershowitz123 Points 3007

A partir de la doc :

Envoi
La fonction libre dispatch_once n'est plus disponible en Swift. En Swift, vous pouvez utiliser des globaux initialisés paresseusement ou des objets statiques de type et obtenir les mêmes garanties de thread-safety et de called-once que 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.

3 votes

Ce n'est pas comme si vous ne saviez pas que Swift évoluerait rapidement et que vous auriez à corriger un grand nombre de codes défectueux entre les versions de Swift.

2 votes

Le plus gros problème est celui des pods de tiers qui ne sont pas toujours compatibles avec Swift3.

4 votes

C'est la dette technique que vous accumulez lorsque vous introduisez des dépendances tierces, @Tinkerbell. J'adore Swift mais je suis très prudent lorsque j'introduis des dépendances externes qui l'utilisent pour cette même raison.

68voto

VaporwareWolf Points 1452

En développant la réponse de Tod Cunningham ci-dessus, j'ai ajouté une autre méthode qui crée automatiquement le jeton à partir du fichier, de la fonction et de la 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()
    }
}

Il peut donc ê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 qu'il peut y avoir une collision si le même fichier se trouve dans deux modules. Dommage qu'il n'y ait pas de #module

0 votes

Ceci nous éclaire un peu plus sur ce qui se passe. Merci de votre compréhension.

0 votes

0 votes

Très utile Merci

21voto

Ryan Heitner Points 1586

Editar

Réponse de @Frizlab - cette solution n'est pas garantie pour la sécurité des threads. Une alternative devrait être utilisée si cela est crucial

La solution simple est la suivante

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

    self.title = "Hello Lazy Guy"

    return
}()

utilisé comme

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

1 votes

Cela ne sert à rien, car une déclaration de var paresseuse ne peut pas être faite en ligne avec du code normal, elle doit se trouver dans une définition de structure ou de classe. Cela signifie que le contenu de dispatchOnce ne peut pas capturer la portée environnante d'une instance. Par exemple, si vous déclarez une fermeture qui n'a pas encore été exécutée, vous ne pouvez pas déclarer la structure à l'intérieur de cette fermeture et faire en sorte que le contenu de la lazy var soit une autre fermeture qui capture les vars de la fermeture environnante...

3 votes

Downvoted because this code has definitely no la même sémantique que dispatch_once. dispatch_once garantit que le code est exécuté exactement une fois, quel que soit le fil de discussion à partir duquel vous l'appelez . Les lazy vars ont un comportement indéfini dans un environnement multithread.

0 votes

Dans cette solution, le bloc init sera appelé deux fois dans certains cas

10voto

mxcl Points 5921

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

Ensuite, 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 être en mesure d'utiliser mxcl_dispatch_once de Swift.

La plupart du temps, vous devriez utiliser ce qu'Apple suggère à la place, mais j'ai eu quelques utilisations légitimes où j'ai eu besoin de dispatch_once avec un seul jeton dans deux fonctions et il n'est pas couvert par ce qu'Apple fournit à la place.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X