dispatch_once in Swift : a 24 hours journey

Jerome
Jerome
Jul 23, 2015 · 4 min read

I love GCD, and dispatch_once is a terrific API: You have the guarantee that your code is gonna be executed once & once only, and on top of that with thread-safety.

I’m learning Swift and the other day, I had to deal with NSNumberFormatter. NSFormatter are great but expensive to instantiate, so you should reuse them as much as possible.

So when I started typing NSNumberFormatter, my Obj-C developer mind told me to use dispatch_once.

I’m greeted with 2 errors:
“Cannot assign to immutable value of type ‘dispatch_once_t’” -> Easy, I’ll just use a var.
“Static properties may only be declared on a type” -> This one is annoying, because I need my variable to be static. Otherwise, what’s the point?

After a few minutes on Google, I ended up coding the ugly-but-working-struct-inside-a-method

func formatSomeStuff() {
struct Static {
static var onceToken: dispatch_once_t = 0
static var numberFormatter: NSNumberFormatter? = nil
}
dispatch_once(&Static.onceToken) {
Static.numberFormatter = NSNumberFormatter()
if let numberFormatter = Static.numberFormatter {
numberFormatter.numberStyle = NSNumberFormatterStyle.PercentStyle
numberFormatter.maximumFractionDigits = 1
}
}
//Do stuff
}

It works but let’s face it, it’s ugly and I had the feeling I was going against Swift.

This “Static properties may only be declared on a type” really annoyed me and I decided to file a radar about this. I was pretty sure it would be closed as duplicate but I filed it anyway. (You will see later why this is important).

Later that day, I attended a Brooklyn Swift meetup http://www.meetup.com/Brooklyn-Swift-Developers/ and one of the slide was about lazy-loading. This looked like a great fit for what I was trying to do, so here we go! (In the meantime, I had move my NSNumberFomatter code into his own method so I can simply use numberFormatter() wherever I needed it).

class MyClass {
lazy var numberFormatter: NSNumberFormatter = {
let numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = NSNumberFormatterStyle.PercentStyle
numberFormatter.maximumFractionDigits = 1
return numberFormatter
}()
func formatStuff() {
//Do stuf with (self.)numberFormatter
}
}

Much Much Better: it starts to finally feel like Swift! Note that I considered having a global var with the same lazy-loaded closure, but as a rule of thumb I avoid putting stuff in the global space (outside of MyClass I don’t need to access my numberFormatter, so the global space is not the right spot for this).

I was still not perfectly happy about it, this numberFormatter is not a property of MyClass, I need it, but it’s not exactly a variable. I chose to define it as a variable only because I wanted to use the lazy-loading.

The next morning I received an email from a Swift Developer at Apple who read my Radar.

Thanks for filing the radar; it’d be great to support static vars in local contexts, I agree. However, you don’t need to manually use dispatch_once to get the same effect. Declare your var as a static in a local struct, and you should get the same effect; all static and global variables are lazily initialized using dispatch_once:

func dateFormatter() -> NSDateFormatter {
struct Static {
static let dateFormatter: NSDateFormatter = NSDateFormatter()
}
return Static.dateFormatter
}

If we supported “static let” directly in a function scope, it would have the same semantics too. dispatch_once is never really necessary in Swift. I hope that helps at least ease the pain of the workaround.

Two main things from this email:
- dispatch_once is never really necessary in Swift. This is the main lesson I learned. Swift provides new ways to do things: you’re not supposed to simply “translate” your app, but you need to learn new things & new awesomeness.
- all static and global variables are lazily initialized using dispatch_once: So… what if instead of a lazy var I used a static let inside my class? It should work!

class MyClass {
static let numberFormatter: NSNumberFormatter = {
let numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = NSNumberFormatterStyle.PercentStyle
numberFormatter.maximumFractionDigits = 1
return numberFormatter
}()
func formatStuff() {
//Do stuf with (self.)numberFormatter
}
}

And Voila! It’s not marked as a var anymore and the intent is clear: It’s a static constant. We benefit under-the-hood from all the greatness of GCD and our code is super readable.

Turns out the dispatch_once & lazy loading relationship was available all along on Apple’s Blog.

The lazy initializer for a global variable (also for static members of structs and enums) is run the first time that global is accessed, and is launched as dispatch_once to make sure that the initialization is atomic. This enables a cool way to use dispatch_once in your code: just declare a global variable with an initializer and mark it private.

Don’t stop your research at the first Stack Overflow answer, try to find a better way to do it (and comment back on that Stack Overflow if you do find a better way to do it).

Jerome

Written by

Jerome

Software Engineer (iOS) @producteev / @jivesoftware

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade