Dispatch_once after changing Swift 3 GCD API

What is the new syntax for dispatch_once in Swift after changes made in language version 3? The old version was next.

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

These are the changes to libdispatch that were made.

+72
swift grand-central-dispatch
Jun 17 '16 at 17:16
source share
9 answers

From doc :

Dispatch
The free dispatch_once function is no longer available in Swift. In Swift, you can use lazily initialized global or static properties and get the same security guarantees for flows and calls as indicated by dispatch_once. Example:

 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. 
+49
Jun 17 '16 at 17:22
source share

When using lazy initialized globals it may make sense for some one-time initialization, this does not make sense for other types. It is very useful to use lazy initialized globals for things like single player games, it doesn't make much sense for things like swizzle setting protection.

Here is a Swatch 3 style implementation of dispatch_once style:

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

Here is a usage example:

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

or using UUID

 private let _onceToken = NSUUID().uuidString DispatchQueue.once(token: _onceToken) { print( "Do This Once!" ) } 

As we are in the process of moving from fast 2 to 3, here is an example implementation of 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() } } 
+88
Jul 11 '16 at 15:49
source share

Extending Tod Cunningham's answer above, I added another method that automatically creates a token from a file, function, and line.

 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 it might be easier to call:

 DispatchQueue.once { setupUI() } 

and you can still specify the token if you want:

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

I suppose you can get a collision if you have the same file in two modules. Sorry no # #module

+46
Oct 11 '16 at 18:08
source share

edit

@Frizlab's answer - this solution does not guarantee thread safety. If important, an alternative should be used.

A simple solution

 lazy var dispatchOnce : Void = { // or anyName I choose self.title = "Hello Lazy Guy" return }() 

used as

 override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() _ = dispatchOnce } 
+12
Apr 6 '17 at 9:44 on
source share

You can use it if you add the bridge title:

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

Then in .m somewhere:

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

Now you can use mxcl_dispatch_once from Swift.

Basically you should use what Apple offers instead, but I had a legal use where I needed to dispatch_once with one token in two functions, and it does not apply to what Apple provides.

+8
Oct 14 '16 at 3:25
source share

Swift 3: for those who love reusable classes (or structures):

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

Using:

 class MyViewController: UIViewController { private let /* var */ setUpOnce = DispatchOnce() override func viewWillAppear() { super.viewWillAppear() setUpOnce.perform { // Do some work here // ... } } } 



Update (April 28, 2017): OSSpinLock replaced with os_unfair_lock due to fatigue warnings 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) } } 
+5
Jan 10 '17 at 13:36 on
source share

You can declare a top-level variable as follows:

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

then call it somewhere:

 doOnce() 
+3
Jun 18. '18 at 13:34
source share

Use the approach with a class constant if you use Swift 1.2 or higher, and the approach with a nested structure if you need to support earlier versions. A study of the Singleton model in Swift. All of the approaches below support deferred initialization and thread safety. The dispatch_once approach does not work in Swift 3.0

Approach A: Class Constant

 class SingletonA { static let sharedInstance = SingletonA() init() { println("AAA"); } } 

Approach B: Nested Structure

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

Approach C: Dispatch

 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! } } 
-one
Dec 28 '16 at 6:57
source share
  I have created below function func executeOnce(code: @escaping () -> Void) { if UserDefaults.standard.value(forKey: "3333##112233") == nil { code() UserDefaults.standard.setValue("vv", forKey: "3333##112233") UserDefaults.standard.synchronize() } } 

And use as below

  executeOnce { print("onces") } 
-6
Aug 09 '17 at 12:00
source share



All Articles