What is the new syntax for dispatch_once
in Swift after the changes made in language version 3? The old version was as follows.
var token: dispatch_once_t = 0
func test() {
dispatch_once(&token) {
}
}
These are the changes to libdispatch that were made.
While using lazy initialized globals can make sense for some one time initialization, it doesn't make sense for other types. It makes a lot of sense to use lazy initialized globals for things like singletons, it doesn't make a lot of sense for things like guarding a swizzle setup.
Here is a Swift 3 style implementation of 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()
}
}
Here is an example usage:
DispatchQueue.once(token: "com.vectorform.test") {
print( "Do This Once!" )
}
or using a UUID
private let _onceToken = NSUUID().uuidString
DispatchQueue.once(token: _onceToken) {
print( "Do This Once!" )
}
As we are currently in a time of transition from swift 2 to 3, here is an example swift 2 implementation:
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()
}
}
From the doc:
Dispatch
The free function dispatch_once is no longer available in Swift. In Swift, you can use lazily initialized globals or static properties and get the same thread-safety and called-once guarantees as dispatch_once provided. 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.
Expanding on Tod Cunningham's answer above, I've added another method which makes the token automatically from 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 can be simpler to call:
DispatchQueue.once {
setupUI()
}
and you can still specify a token if you wish:
DispatchQueue.once(token: "com.hostname.project") {
setupUI()
}
I suppose you could get a collision if you have the same file in two modules. Too bad there isn't #module
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With