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).