Even though I've written about the woes of managing state in my previous post, sometimes there's just no way we can avoid it. One example of managing state is something we're all quite acquainted with - The Singleton. The problem we find in Swift is that there are SEVERAL ways of implementing them. But which way is the right way? In this post I'm going to show you the history of the singleton and then show you the right way to implement the singleton in Swift.
If you want to see the right way to implement the singleton pattern in Swift along with proof of it's "right-ness", you can scroll to the bottom of the post and see it there. :)
A TRIP DOWN MEMORY LANE
Swift is a natural evolution of Objective-C. In Objective-C, this is how we implemented the singleton:
@interface Kraken : NSObject
@end
@implementation Kraken
+ (instancetype)sharedInstance {
static Kraken *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[Kraken alloc] init];
});
return sharedInstance;
}
@end
Now that we have that out of the way and we can see the basic structure of a singleton, let's lay out some rules so we understand what we're looking at:
DA RULES OF DA SINGLETON, MAHN
There are essentially three things to remember about the Singleton:
- A singleton has to be
unique
. This is why it's called asingleton
. There can only be one instance for the lifetime of the application it exists in. Singletons exist to give us singular global state. Such examples areNSNotificationCenter
,UIApplication
, andNSUserDefaults
. - To maintain a singleton's unique-ness, the initializer of a singleton needs to be private. This helps to prevent other objects from creating instances of your singeton class themselves. Thank you to all who pointed that out to me :)
- Because of rule #1, in order to have only one instance throughout the lifetime of the application, that means it needs to be
thread-safe
. Concurrency really sucks when you think about it, but simply put, if a singleton is built incorrectly in code, you can have two threads try to initialize a singleton at the same time which can potentially give you two separate instances of a singleton. This means that it's possible for it to not be unique unless we make itthread-safe
. This means we want to wrap the initialization in adispatch_once
GCD block to make sure the initialization code only runs once at runtime.
Being unique and initializing in one place in an app is easy. The important thing to remember for the rest of this post is that a singleton fulfill the much-harder-to-see dispatch_once
rule.
DA SWIFT SINGLETON
Since Swift 1.0, there have been several ways to create a singleton. These have been covered very extensively here, here, and here. But who likes clicking on links? SPOILER ALERT; There are four variations. Allow me to count the ways:
THE UGLIEST WAY (A.K.A. THE "WHY ARE YOU STILL CODING IN SWIFT IF YOU'RE JUST GOING TO DO THIS" WAY)
class TheOneAndOnlyKraken {
class var sharedInstance: TheOneAndOnlyKraken {
struct Static {
static var onceToken: dispatch_once_t = 0
static var instance: TheOneAndOnlyKraken? = nil
}
dispatch_once(&Static.onceToken) {
Static.instance = TheOneAndOnlyKraken()
}
return Static.instance!
}
}
This way is a straight port of Objective-C's singleton implementation over to Swift. Ugly in my opinion because Swift was meant to be terse and expressive. Be better than the port guys. Be better. :P
THE STRUCT WAY (A.K.A. THE "OLD BUT STRANGELY STILL POPULAR" WAY)
class TheOneAndOnlyKraken {
class var sharedInstance: TheOneAndOnlyKraken {
struct Static {
static let instance = TheOneAndOnlyKraken()
}
return Static.instance
}
}
This way was how we had to do it in Swift 1.0 since classes still didn't support static class variables back then. Structs, however, did support them! Because of these restrictions on static variables, we were forced into a model that looked like this. It's better than the straight Objective-C port but still not good enough. Funnily enough, I still see this method of writing singletons several months after the release of Swift 1.2. But more on that later.
THE GLOBAL VARIABLE WAY (A.K.A. THE "ONE LINE SINGLETON")
private let sharedKraken = TheOneAndOnlyKraken()
class TheOneAndOnlyKraken {
class var sharedInstance: TheOneAndOnlyKraken {
return sharedKraken
}
}
As of Swift 1.2, we gained access control specifiers
and the ability to have static class members
. This meant that we didn't have to have a global variable clutter the global namespace and we could now prevent namespace collisions. This version is a lot Swiftier in my opinion.
Now at this point, you may be asking why we don't see dispatch_once
in our struct or global variable implementations. Well according to Apple, both of these methods fulfills the dispatch_once
clause I outlined above. Here's a quote straight from their Swift Blog that proves that they are wrapped in dispatch_once
blocks behind the scenes:
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 usedispatch_once
in your code: just declare a global variable with an initializer and mark it private.
— Apple's Swift Blog
That's all Apple gave us as far as official documentation goes. But this meant that all we had proof for was global
variables and static members of structs/enums! At that point, the only 100% safe bet backed by Apple docs was using a global variable to lazily wrap singleton initialization in a dispatch_once
block. BUT WHAT ABOUT OUR STATIC CLASS VARIABLES?!?!?!?
This very question brings us to this next exciting section:
THE RIGHT WAY A.K.A. "THE ONE LINE SINGLETON (NOW WITH PROOF!")
class TheOneAndOnlyKraken {
static let sharedInstance = TheOneAndOnlyKraken()
}
So I've done a fair amount of research for this post. In fact, this post was inspired by a conversation we had at Capital One today due to the review of a PR that aimed at achieving proper singleton consistency in Swift across our app. We knew about this "right" method of writing singletons, but we had no proof to back up our reasoning other than postulation. Trying to back this method up without sufficient documentation was useless. It was my word against a lack of information on the internet/blogosphere. And everyone knows that if it isn't on the Internet it isn't true. This made me sad.
I navigated to the far ends of the internets (AKA the 10th page of Google Search Results) and came up empty handed. Had no one posted proof of the one line singleton yet?! Maybe they have, but it was hard to find.
So I decided to do something about it and wrote up every way of initializing a singleton and inspected them at runtime using breakpoints. After analyzing each stack trace for any similarities I came across something interesting - PROOF!
Check it out, yo (Oh, and yay for class emojis!):
The first image shows the stack trace of a global let instantiation. Outlined in red is the thing of interest here. Before the actual initialization of the Kraken singleton is a call trace labeled swift_once
followed by a swift_once_block_invoke
call. Since Apple said they lazily instantiate global variables in a dispatch_once
block, we can safely assume this is what they meant.
Using this knowledge, I inspected the stack trace of our shiny & pretty one-line-singleton. As you can see with our second image, it's exactly the same! So there you have it! Proof that our one line singleton is proper. All is now right with the world. Also, now that this post is on the Internet, that MUST mean it's true!
wink wink
DON'T FORGET THE PRIVATE INIT!
As @davedelong, Frameworks Evangelist at Apple, graciously pointed out to me, you have to make sure that your inits are private
. This makes sure your singletons are truly unique and prevents outside objects from creating their own instances of your class through virtue of access control. Since all objects come with a default public initializer in Swift, you need to override your init and make it private. This isn't too hard to do and still ensures our one line singleton is nice and pretty:
class TheOneAndOnlyKraken {
static let sharedInstance = TheOneAndOnlyKraken()
private init() {} //This prevents others from using the default '()' initializer for this class.
}
Doing this will makes sure that the compiler throws this error when any class attempts to initialize TheOneAndOnlyKraken
using the () initializer:
And there you have it! The perfect, one-line singleton.
CONCLUSION
Echoing jtbandes' excellent comment on the top rated answer to swift singletons on Stack Overflow, I simply could not find documentation anywhere that proved thread-safety by "virtue of let". I actually remember something of the sort said when I was at WWDC last year, but you can't expect readers or quick Googlers to stumble across that when trying to make sure this is the right way to write a singleton in Swift. Hopefully, this post can help someone out there understand why the one-line singleton in Swift is the way to go.
转自: