Whither dispatch_once in Swift 3?
Asked Answered
J

7

59

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.")
    }
}
Joaquin answered 14/6, 2016 at 1:2 Comment(0)
J
61

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.

Joaquin answered 14/6, 2016 at 1:2 Comment(4)
Considering that Singleton "is a design pattern that restricts the instantiation of a class to one object", wouldn't be nice to add private to the class' constructors? Notice that if have a variable in this class you could have multiple instances holding a different value for this variable. Once the constructor is private, only the static let singleton line would be able initialize it.Deaconry
What if I've to use an instance variable in the dispatch_once block?Exempt
@InderKumarRathore : there's actually a lot more to that comment than meets the eye. Why not post it as a separate question?Joaquin
@Joaquin I've posted in on apple developers fourm after searching on SO. I'll post the solution as soon as I get it. Here is the post : forums.developer.apple.com/thread/69433?sr=streamExempt
I
22

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.

Irrawaddy answered 7/3, 2017 at 16:52 Comment(3)
same minor caveat as simply using Void: if someone writes doSomethingOnce = {} before the first call, it will not call it even once.Speech
Please advise if there is a way to remove the compiler error that is generated at the site of declaration of the "doSomethinOnce" if the code it contains can throw and exception. "lazy var doSomethingOnce: () throws -> Void"Semination
@user2196409 The errors thrown from the function can be handled at the call site by marking the call with the try keyword, just as with normal throwing functions (I checked this in Swift 4.2).Irrawaddy
F
17

The other answers here and around the interwebs are pretty great, but I feel this little tidbit should also be mentioned:

The great thing about dispatch_once was how optimized it was, essentially nixing the code after the first run in a way that I hardly understand, but am reasonably assured that would be that much faster than setting and checking a (real) global token.

While the token thing could be reasonably implemented in Swift, having to declare yet another stored boolean isn't all that great. Not to mention thread-unsafe. As the doc says, you should use a "lazily initialized global." Yeah, but why clutter up the global scope, right?

Until somebody convinces me of a better method, I tend to declare my do-once closure inside the scope I'll be using it in, or reasonably close thereto, as follows:

private lazy var foo: Void = {
    // Do this once
}()

Basically I'm saying that "When I read this, foo should be the result of running this block." It behaves the exact same way as a global let constant would, just in the right scope. And prettier. Then I'd call it wherever I'd like, by reading it into something that'll never be used otherwise. I like Swift's _ for that. Like so:

_ = foo

This really cool quirk has actually been around a while, but hasn't seen much love. It basically leaves the variable alone at runtime, as an uncalled closure, until something wants to see its Void result. On read, it calls the closure, throws it away and keeps its result in foo. Void uses practically nothing memory-wise, so subsequent reads (i.e. _ = foo) do nothing on the CPU. (Don't quote me on that, somebody please check up on the assembly to be sure!) Have as many as you like, and Swift basically quits caring about it after the first run! Lose that old dispatch_once_t, and keep a lot of your code as pretty as when you first opened it Christmas day!

My one issue is that you can set foo to something else before its first read, and then your code will never be called! Hence the global let constant, which prevents that. Thing is, constants in class scope don't play well with self, so no playing with instance variables... But seriously, when do you set anything to Void anyway??

That, and you'd need to specify the return type as Void or (), else it'll still complain about self. Who'da thunk?

And lazy is just to make the variable as lazy as it should be, so Swift doesn't run it straight off on init().

Pretty snazzy, so long as you remember not to write to it! :P

Fundamental answered 16/9, 2016 at 22:52 Comment(1)
There is a subtle difference between your and @Joaquin approaches. Your "lazily initialized locals" once action is done once per instance, while "lazily initialized globals" once action is done once per type. See this gistMacmacabre
R
8

Example for "dispatch_once" in Swift 3.0

Step 1 : Just Replace below code with your Singleton.swift (Singleton class)

// Singleton Class
class Singleton: NSObject { 
var strSample = NSString()

static let sharedInstance:Singleton = {
    let instance = Singleton ()
    return instance
} ()

// MARK: Init
 override init() {
    print("My Class Initialized")
    // initialized with variable or property
    strSample = "My String"
}
}

Singleton Sample Image

Step 2 : Call Singleton from ViewController.swift

// ViewController.swift
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        let mySingleton = Singleton.sharedInstance
        print(mySingleton.strSample)

        mySingleton.strSample = "New String"
        
        print(mySingleton.strSample)
        
        let mySingleton1 = Singleton.sharedInstance
        print(mySingleton1.strSample)

    }

ViewController Sample Image

Output like this

My Class Initialized
My String
New String
New String
Reset answered 5/1, 2017 at 8:18 Comment(1)
Could you please explain why this is only dispatched once? why wouldn't a race between 2 threads accessing the sharedInstance collide? Also, being a "static" it is initialized at some unknown time - whether or not we need it. The nice thing about dispatch_once, is that I could control if/when it is executed.Ripplet
E
6

Compiles under Xcode 8 GA Swift 3

The recommended and elegant way to create a dispatch_once singleton class instance:

final class TheRoot {
static let shared = TheRoot()
var appState : AppState = .normal
...

To use it:

if TheRoot.shared.appState == .normal {
...
}

What do these lines do?

final - so the class cannot be overridden, extended, it also makes the code somewhat faster to run, less indirections.

static let shared = TheRoot() - This line does lazy init, and it only runs once.

This solution is thread safe.

Eft answered 22/8, 2016 at 15:1 Comment(1)
Can you please explain how come this is "lazy init"? my experience with static vars in other languages, is that the compiler inserts the initialization code for them at some arbitrary time, near loading. Never lazy, as far as I know. Where is the definition for that in swift?Ripplet
K
4

According to the Migration Guide:

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

  // using myGlobal will invoke the initialization code only the first time it is used.
  _ = myGlobal  
Kuopio answered 22/8, 2016 at 18:38 Comment(0)
A
-3

Thread-safe dispatch_once:

private lazy var foo: Void = {
    objc_sync_enter(self)
    defer { objc_sync_exit(self) }

    // Do this once
}()
Ajani answered 10/4, 2018 at 18:38 Comment(2)
the existing lazy mechanism is thread-safe already (in the sense that it executes at most once). This extra bit of "thread safety" does not help, and may hurt.Decolonize
@Decolonize For the following variable private lazy var foo: Void = { print("hello") }() this code will print "hello" multiple times (reproduced on simulator only): for _ in 0 ... 1000 { DispatchQueue.global(qos: .default).async { _ = self.foo } } }Ajani

© 2022 - 2024 — McMap. All rights reserved.