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