Everyone has heard of singletons. It’s probably the simplest design pattern there is, yet, there are a few catches when implementing it. In this post, I’ll go over the singleton design pattern and the little gotchas that might end up in a crash or an inconsistent state of the app.
Singleton Design Pattern
Singleton design pattern ensures that a class has one, and only one instance, and it provides a global access point to it. There are many books written on the subject of design patterns. My favourite is Head First Design Patterns, the most popular one is Design Patterns by the Gang of Four. You can read more on design patterns in those two books.
People often abuse singletons, because it’s very tempting to have a class that’s globally accessible, and if you start using singletons for everything, your project will spiral into a big uncontrollable bug machine.
Let’s say you have a news reader app, and you want to save the currently selected article. If you save that article in a singleton, it will be impossible for you to track who owns the object, and control the changes on it. You’ll see weird bugs in your app where the app would seem to randomly change the selected article. Debugging this piece of code will be very difficult… A fine example when not to use a singleton.
Singletons obviously exist for a reason, and there are times when they are the perfect choice for you. If you need to control an access to a shared resource, like app settings, or the keychain, singletons are a perfect choice.
The Achilles heel of singletons is multithreading. You need to be very careful when implementing singletons in a multithreading environment. And since we’re working on iOS here, this applies to us. In order not to corrupt our data, we need to control the write access in the singleton. One of the ways we can do this is to raise a dispatch barrier.
I assume you know a thing or two about multithreading, so I’ll get to the point. A dispatch barrier will ensure that a piece of code will be executed, and while it’s being executed, no other piece of code will be executed. This is very important. With this simple GCD flag, we’ll be able to solve our problems.
Let’s dive into our code example.
Code Example
Previously I wrote an article about a simple keychain wrapper. In this article, we’ll modify the code I wrote previously and follow-up on the changes necessary to make the class a true singleton.
In the old version of DAKeychain we had a static variable that we instantiated with our class, like so:
public static let shared = DAKeychain()
This is called ‘eager instantiation’, where we instantiate an object even if we might not end up using it. Creating singletons this way is perfectly fine, and it’s thread safe. The downside of this approach is, the class get’s loaded in memory even if it’s never used.
Now that we have our shared variable, we can use our singleton:
DAKeychain.shared["key"] = "confidential data" // Store let value = DAKeychain.shared["key"] // Fetch
We said in our definition of singletons that there can only be one. And there’s nothing stopping us from creating more instances of the keychain object, like so:
let keychain = DAKeychain()
We can use a simple trick with access modifiers to solve this problem. All we need to do is make the constructor private, and that’s it:
private init() {}
Now when we try to create another instance of the keychain object, we get a compile error, because swift can’t find the constructor for the object:
Perfect, now we have complete control over the creation of the instances, and we know that there’s going to be only one.
Lazy Instantiation
We’ll cover another example where we create our class when we need it. This is called ‘lazy instantiation’. Let’s see the code first, and then I’ll explain it:
private init() {} private static var _shared: DAKeychain? public static var shared: DAKeychain { get { if _shared == nil { DispatchQueue.global().sync(flags: .barrier) { if _shared == nil { _shared = DAKeychain() } } } return _shared! } }
We have our private constructor at the top and a private static variable where we’ll keep our instance. In the getter of the ‘shared’ variable, we’re checking to see if we already have a shared instance. If we do, we return it, if we don’t, we create it. We have to be careful here because this is not a thread safe way of creating singletons. You might actually end up having multiple instances of a singleton, let’s see how on this diagram:
Go through the code above, and follow the diagram. Thread 1 will check the shared instance and it will see that it’s nil, then it will create a new object and assign it to that shared instance, at the same time Thread 2 will do the same, but let’s assume the Thread 2 is running slightly after Thread 1. In that time window where Thread 1 checks if the instance is nil and assigns a new object to the shared instance Thread 2 will enter the if statement because the shared instance is still not being set. This can easily be solved with a dispatch barrier. We simply raise the barrier to make sure no code is being executed while the object is being created, once we raise the barrier we check again if the shared instance is nil (if we don’t we would still end up in the same scenario depicted on the graph above… thanks, mbarnach 🙂 ). Now object creation time no longer plays a role in our lazy instantiation, and it’s not a problem.
Just to clean up our keychain a bit, we need to control the write access. We don’t want multiple threads writing into the keychain at the same time. Since we’re using subscripting this is actually pretty simple to do:
subscript(key: String) -> String? { get { return load(withKey: key) } set { DispatchQueue.global().sync(flags: .barrier) { self.save(string: newValue, forKey: key) } } }
So we just raise the dispatch barrier when saving.
Conclusion
We examined one of the most popular patterns. There are some caveats when using it, but if you’re aware of them, it’s pretty easy to use. This has been a short and a fun post, I hope you’ll find it useful. And as usual, you can find all the code on my GitHub repo.
Have a nice day 🙂
Dejan.
Your barrier is mis-placed or (better) you need to re-check the value of _shared after the barrier. Otherwise the same issue could still apply, if e.g. thread 2 is slower than thread 1.
You are absolutely right, I can’t believe I’ve missed that. Thanks a lot for pointing it out, I’ve updated the code and the main text. Now I know that at least one person read the post 🙂
You could also create the instance on the main thread, which is serial by nature.
You could do it on the main thread, but personally I try to do as much as possible off the main thread, and only do UI interaction on the main thread.
So, what’s wrong with old good :
class Singleton {
private init() { }
static let sharedInstance: Singleton = Singleton()
}
Hi Mixas, like I mentioned in the post, the old approach is just fine if you don’t mind instantiating a class every time your app is run. If your class is resource intensive, or you simply don’t want to instantiate a singleton every time you run the app then you can use the lazy instantiation as described above. Like you pointed out, in most cases the good old approach will be just fine 🙂
According to the docs at https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html, such static poperties are lazily instantiated and are guaranteed to be initialised only once even when accessed from different threads. So the good old approach does what you want it to do.
Pingback: Design Patterns in Swift: Singleton – Home
Hi Dejan,
Nice article and well written, Just as an alternative I would use a custom concurrent Q rather than one of the global Q’s to run the dispatch barrier so as not to hold up the system using them.Or a custom serial Q and dispatch async onto it the write operation.
Great that swift allows overloading the subscript and other operators reminds me of the my C++ days.
Cheers,
Derek
Hey Derek,
That’s a great idea, I love it! It will be more performant and won’t block the main queue, thanks 🙂
Have a nice day 🙂
Dejan.
“static let shared = Singleton()” is lazy and thread-safe already. However, if you are seeing that the property is being instantiated before use, the reality may be that it is being used somewhere without intent. It could be you are seeing this Swift bug (or is it intentional?): https://bugs.swift.org/plugins/servlet/mobile#issue/SR-1178
Also I love how you used the barrier flag for the initialization. Since the barrier flag blocks reads and writes when writing, the real benefit over a simple serial queue is that it still allows reads to occur concurrently when there isn’t a write in progress.
Hey Basem,
Thank you for the link. It’s interesting that the getter for the static vars is being called even when you set the variable. Do you believe that would be a problem in this implementation of singleton.
Thanks for the comment on barriers on reads. If you remember, there was a WWDC talk one year where they talked about concurrency in Core Data and they mentioned that the read operations are synchronised. That’s where I got the idea from 🙂
Thank your for reading the article and have a nice day 🙂
Dejan.
No your implementation is great and doesn’t suffer from the Swift bug. Coincidently I used this technique for dependency injection, except now I’m stealing your barrier flag 😉
Sharing is caring, right 😀
I’m glad the post helped you out 😉
Well explained. Thanks for this article.
Thanks, I appreciate it 🙂