I want to use the singleton pattern in my class which has a private init
with parameter. It also has a class function called setup
which configures and creates the shared instance. My objective-c code would be:
@interface MySingleton: NSObject + (MySingleton *)setup:(MyConfig *)config; + (MySingleton *)shared; @property (readonly, strong, nonatomic) MyConfig *config; @end @implementation MySingleton static MySingleton *sharedInstance = nil; + (MySingleton *)setup:(MyConfig *)config { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[self alloc] initWithConfig:config]; }); // Some other stuff here return sharedInstance; } + (MySingleton *)shared { if (sharedInstance == nil) { NSLog(@"error: shared called before setup"); } return sharedInstance; } - (instancetype)initWithConfig:(RVConfig *)config { self = [super init]; if (self) { _config = config; } return self; } @end
I am stuck with Swift:
class Asteroid { var config: ASTConfig? // This actually should be read-only class func setup(config: ASTConfig) -> Asteroid { struct Static { static let instance : Asteroid = Asteroid(config: config) } return Static.instance } class var shared: Asteroid? { // ??? } private init(config: ASTConfig) { self.config = config } }
I think I am still thinking in objective-c way and couldn't figure it out with swift. Any help?
The parametric singleton pattern allows for one instance of a class for a given set of parameters. It provides a single point of global access to a class, just the way the singleton pattern does [Cooper]. The difference is that, for each new parameter, another instance is created.
However, singleton is a principle in Java that can only be created when: A private class has a default constructor. Any protected static class type object is declared with the null value. A parameter is assigned to the constructor of the singleton class type (as we did in step two).
To create a singleton class using Eager Initialization method, we need to follow the below steps: Declare the constructor of the class as private. Now, create a private class member for this Singleton class. In the next step, you need to define a factory method that will return the object of the Singleton class.
By using singletons in your project, you start to create technical debt. Singletons tend to spread like a virus because it's so easy to access them. It's difficult to keep track of where they're used and getting rid of a singleton can be a refactoring nightmare in large or complex projects.
I have a slightly different solution. This relies on
.
class MySingleton { static let shared = MySingleton() struct Config { var param:String } private static var config:Config? class func setup(_ config:Config){ MySingleton.config = config } private init() { guard let config = MySingleton.config else { fatalError("Error - you must call setup before accessing MySingleton.shared") } //Regular initialisation using config } }
To use this, you set it up with
MySingleton.setup(MySingleton.Config(param: "Some Param"))
(Obviously you can use multiple params if needed by expanding the MySingleton.Config struct)
Then to access the singleton, you use
MySingleton.shared
I'm not wild about having to use a separate setup struct, but I like that this stays close to the recommended singleton pattern. Keeping the setup struct inside the singleton keeps things fairly clean.
Note - the shared object is a singleton. In the background, swift uses dispatchOnce to guarantee that. However there is nothing stopping you from calling setup multiple times with different configs from different threads.
At the moment, the first call to shared will 'lock' the setup.
If you want to lock things down after the first call to setup, then just call
_ = MySingleton.shared
in setup
Simple Example:
class ServerSingleton { static let shared = ServerSingleton() struct Config { var host:String } private static var config:Config? let host:String class func setup(_ config:Config){ ServerSingleton.config = config } private init() { guard let config = ServerSingleton.config else { fatalError("Error - you must call setup before accessing MySingleton.shared") } host = config.host } func helpAddress() -> String { return host+"/help.html" } } ServerSingleton.setup(ServerSingleton.Config(host: "http://hobbyistsoftware.com") ) let helpAddress = ServerSingleton.shared.helpAddress() //helpAddress is now http://hobbyistsoftware.com/help.html
A literal translation of your Objective-C code might be:
private var _asteroidSharedInstance: Asteroid! class Asteroid { private var config: ASTConfig? class func setup(config: ASTConfig) -> Asteroid { struct Static { static var onceToken: dispatch_once_t = 0 } dispatch_once(&Static.onceToken) { _asteroidSharedInstance = Asteroid(config: config) } return _asteroidSharedInstance } class var sharedInstance: Asteroid! { // personally, I'd make this `Asteroid`, not `Asteroid!`, but this is up to you if _asteroidSharedInstance == nil { println("error: shared called before setup") } return _asteroidSharedInstance } init(config: ASTConfig) { self.config = config } }
Or, in Swift 1.2, you could eliminate that Static
struct and simplify setup
a bit:
private static var setupOnceToken: dispatch_once_t = 0 class func setup(config: ASTConfig) -> Asteroid { dispatch_once(&setupOnceToken) { _asteroidSharedInstance = Asteroid(config: config) } return _asteroidSharedInstance }
This really isn't a singleton. (I suspect you know this, but I mention that for the benefit of future readers). Typically singletons can be instantiated wherever and whenever they're first used. This is a scenario where it's being instantiated and configured in only one particular place and you must take care to do this before you try to use it elsewhere. That's very curious approach. We lose some singleton functionality, but still suffer all of the traditional singleton limitations.
Clearly, if you're ok with that, that's fine. But if you're entertaining alternatives, two jump out at me:
Make this real singleton: You can accomplish this (eliminating the dependency of having to call setup
before you use sharedInstance
) by moving the instantiation of the ASTConfig
inside the init
method. Then you can retire setup
and just use your singleton like normal. The resulting implementation is greatly simplified, too. It gets reduced down to something like:
class Asteroid { static let sharedInstance = Asteroid() private let config: ASTConfig init() { self.config = ASTConfig(...) } }
Clearly, I suspect the devil is in the details of that ASTConfig
object, but if you can do a proper singleton implementation, as you can see this is much simpler (esp. in Swift 1.2). And the above eliminates the setup
vs sharedInstance
problem. Eliminates the private global. Just simpler all the way around.
Having said that, I assume you had compelling reasons to do it the way you did. Perhaps there is some critical reason why you must pass ASTConfig
object to setup
method rather than just instantiating it yourself within the init
of the Asteroid
class.
I just felt obliged to point out that a proper singleton would be greatly preferable (both much simpler implementation and eliminates theoretical race conditions).
Abandon singleton pattern entirely: Assuming using a proper singleton, as outlined above, is not possible, the next question is whether you should just abandon any remaining semblance of a singleton, just instantiate a simple Asteroid
where you are currently calling setup
, and then rather than relying upon sharedInstance
, just pass it to the objects that really need it.
You already have specified that you're going to manually setup
the Asteroid
up front, so let's formalize that relationship and eliminate many of the structural flaws that singletons introduce (see What's Alternative to Singleton or google "singletons are evil").
Don't get me wrong. I assume that you have compelling reasons to do it the way you have, and if the current implementation works for you, that's fine. But this is a very curious approach, in which you're encumbered with the theoretical liability of singletons without enjoying all the benefits.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With