Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

dispatch_once after the Swift 3 GCD API changes

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.

like image 248
David Avatar asked Jun 17 '16 17:06

David


3 Answers

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

}
like image 67
Tod Cunningham Avatar answered Nov 03 '22 00:11

Tod Cunningham


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.
like image 74
Mtoklitz113 Avatar answered Nov 02 '22 23:11

Mtoklitz113


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

like image 68
VaporwareWolf Avatar answered Nov 03 '22 01:11

VaporwareWolf