Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Whither dispatch_once in Swift 3?

Okay, so I found out about the new Swifty Dispatch API in Xcode 8. I'm having fun using DispatchQueue.main.async, and I've been browsing around the Dispatch module in Xcode to find all the new APIs.

But I also use dispatch_once to make sure that things like singleton creation and one-time setup don't get executed more than once (even in a multithreaded environment)... and dispatch_once is nowhere to be found in the new Dispatch module?

static var token: dispatch_once_t = 0 func whatDoYouHear() {     print("All of this has happened before, and all of it will happen again.")     dispatch_once(&token) {         print("Except this part.")     } } 
like image 961
rickster Avatar asked Jun 14 '16 01:06

rickster


People also ask

What is Dispatch_once 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 … }()

What is dispatch once?

Executes a block object only once for the lifetime of an application.


2 Answers

Since Swift 1.x, Swift has been using dispatch_once behind the scenes to perform thread-safe lazy initialization of global variables and static properties.

So the static var above was already using dispatch_once, which makes it sort of weird (and possibly problematic to use it again as a token for another dispatch_once. In fact there's really no safe way to use dispatch_once without this kind of recursion, so they got rid of it. Instead, just use the language features built on it:

// global constant: SomeClass initializer gets called lazily, only on first use let foo = SomeClass()  // global var, same thing happens here // even though the "initializer" is an immediately invoked closure var bar: SomeClass = {     let b = SomeClass()     b.someProperty = "whatever"     b.doSomeStuff()     return b }()  // ditto for static properties in classes/structures/enums class MyClass {     static let singleton = MyClass()     init() {         print("foo")     } } 

So that's all great if you've been using dispatch_once for one-time initialization that results in some value -- you can just make that value the global variable or static property you're initializing.

But what if you're using dispatch_once to do work that doesn't necessarily have a result? You can still do that with a global variable or static property: just make that variable's type Void:

let justAOneTimeThing: () = {     print("Not coming back here.") }() 

And if accessing a global variable or static property to perform one-time work just doesn't feel right to you -- say, you want your clients to call an "initialize me" function before they work with your library -- just wrap that access in a function:

func doTheOneTimeThing() {     justAOneTimeThing } 

See the migration guide for more.

like image 51
rickster Avatar answered Sep 30 '22 02:09

rickster


While the "lazy var" pattern allows me to stop caring about dispatch tokens and is generally more convenient than dispatch_once() was, I don't like what it looks like at the call site:

_ = doSomethingOnce 

I would expect this statement to look more like a function call (since it implies action), but it doesn't look so at all. Also, having to write _ = to explicitly discard the result is annoying.

There is a better way:

lazy var doSomethingOnce: () -> Void = {   print("executed once")   return {} }() 

Which makes the following possible:

doSomethingOnce() 

This might be less efficient (since it calls an empty closure instead of just discarding a Void), but improved clarity is totally worth it for me.

like image 32
Andrii Chernenko Avatar answered Sep 30 '22 03:09

Andrii Chernenko