Fundamental Design Pattern: Singleton
Cracking the Singleton Design Pattern
Design patterns that will be covered in this article:
The Singleton pattern restricts a class to only 1 instance. Every reference to the class refers to the same underlying instance. The Singleton pattern is categorized as a creational pattern because the design pattern is all about creating a shared instance.
The Singleton Plus pattern is also common, which provides a shared or default Singleton instance but it also allows other instances to be created too.
When Should You Use It?
- Use the Singleton pattern when having more than 1 instance of a class would cause problems or when it just wouldn’t make logical sense
- Use the Singleton Plus pattern if a shared instance is useful most of the time, but you also want to allow custom instances to be created — An example of a Singleton Plus pattern is File Manager (this handles everything to do with file system access). There is a default instance, which is a Singleton or you can create your own (for instance, on a background thread)
What Should You Be Careful About?
- The Singleton pattern is very easy to overuse
- It’s not recommended to use Singletons if you’re simply trying to pass information from 1 view controller to another
- If you do determine you actually do need a Singleton, consider whether or not a Singleton Plus would make more sense. If a Singleton really is best, prefer to use a Singleton plus over a Singleton
- Testing is a common reason why Singletons are problematic — if you have a state being stored in a global object like a Singleton, then order of tests can matter and it can be painful to unlock them
- Be careful of “code smells” which means that your use case isn’t appropriate as a Singleton at all
What makes AnimeSettings a Singleton is its private init() method. If you were to change the private access control to public, then AnimeSettings would be a Singleton Plus. Similar to UIApplication, AnimeSettings also has a static shared property that refers to its instantiated self.
We created an enum called AnimeStrategyType and made it conform to Int & CaseIterable. The enum has 2 cases and 2 methods that uses the cases to return a String and AnimeDetailStrategy.
UserDefaults is another example of a Singleton and we create a computed property called animeStrategyType. Within the getter of the property, we use the single instance of UserDefaults to call its integer method which allows us to insert, in this case, a key with any random value. We then are able to return a force unwrapped instance of AnimeStrategyType with an initializer of rawValue (this is due to having the enum AnimeStrategyType conform to Int).Within the setter of the property, we are setting whatever value we want to store in UserDefaults by replacing it with the value stored in the key we initially used in the getter.
AnimeSettingsViewController is your typical UITableViewController utilizing the UITableViewDataSource methods such as numberOfRowsInSection & cellForRowAt and UITableViewDelegate methods such as didSelectRowAt.
We create an object of the AnimeSettings singleton — animeSettings and if you remember, AnimeSettings.shared is a static property we wrote that refers to the instantiated version of the class itself.
By having our enum AnimeStrategyType conform to CaseIterable, we can iterate through the cases declared within the enum by using .allCases as we have done above in the numberOfRowInSection method. We set the Reuse Identifer of the UITableViewCell of AnimeSettingsViewController in Main.storyboard to AnimeStrategy to ultimately create a dequeueReusableCell.
What’s important here is that we check if animeSettings.animeStrategyType is equal to a case declared within our AnimeStrategyType enum, which calls upon the getter of our animeStrategyType computed property. If there is a match, we set the cell’s accessory type to have a check mark, otherwise, we set the cell’s accessory type to none.
Lastly, the setter of our computed property within our Singleton class, animeSettings.animeStrategyType, gets set in the didSelectRowAt method of the table view. This ultimately saves the newly selected row or enum case in UserDefaults, so if the user terminates and re-runs the app, he or she will see their previous selected row, in this case, have a check mark.
I inserted a UIBarButtonItem at the top left corner of the Anime View Controller, and when the bar button item is tapped, a segue with an action of Show will occur to move to the Anime Settings View Controller.
Also, make sure to set the AnimeSettingsViewController’s class in the identity inspector with the name of the class in your corresponding code file, in this case AnimeSettingsViewController.swift.
In case if you haven’t yet, join my Discord server! I’m looking to create a community of developers so we can reinforce, motivate, and get to know each other in our programming journey.
If you have any questions or comments, please don’t feel afraid to ask or connect with me on social media! You can also send me an email at firstname.lastname@example.org