36

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?

knshn
  • 3,401
  • 1
  • 21
  • 22

7 Answers7

50

I have a slightly different solution. This relies on

  1. Static variable is lazily initialised
  2. Using a Config struct to store the initialisation params
  3. Enforcing the setup call with a fatalError in init (if the setup call isn't called before accessing the singleton)

.

class MySingleton {

    static let shared = MySingleton()
    
    struct Config {
        let 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 {
        let 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
Confused Vorlon
  • 9,659
  • 3
  • 46
  • 49
  • 1
    What if i want to make param let and not var ? – Mihir Mehta Sep 27 '18 at 11:41
  • I was getting error while loading swift code through PlatformView on Flutter, and getting 'Reinitialization is not allowed' error on swift side, seems like it happened because flutter rebuild makes swift to create another instance of platformView, If somebody is also facing this issue, solution is above – 1encore Feb 08 '23 at 10:54
32

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:

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

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

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
5

You can define a singleton that takes one or more parameters initially by making the static sharedInstance property private and using a method to return either the existing instance (optionally changing its property value(s)), or initialing a new instance and setting its property values. For example, and I've also made your config property read-only:

typealias ASTConfig = String

class Asteroid  {

    private static var sharedInstance: Asteroid!

    var config: ASTConfig?

    private init(config: ASTConfig?) {
        self.config = config
        Asteroid.sharedInstance = self
    }

    static func shared(config: ASTConfig? = "Default") -> Asteroid {
        switch sharedInstance {
        case let i?:
            i.config = config
            return i
        default:
            sharedInstance = Asteroid(config: config)
            return sharedInstance
        }
    }
}

let asteroidA = Asteroid.shared()

asteroidA.config // Default

let asteroidB = Asteroid.shared(config: "B")

asteroidA.config // B

You could make your config property read-only by defining its setter as private...

private(set) var config: ASTConfig?

...but callers of shared(config:) would still be able to change the config. To prevent that, you'll need to make shared(config:) a throwing method:

typealias ASTConfig = String

class Asteroid  {

    enum E : Error {
        case config(message: String)
    }

    private static var sharedInstance: Asteroid!

    private(set) var config: ASTConfig?

    private init(config: ASTConfig?) {
        self.config = config
        Asteroid.sharedInstance = self
    }

    static func shared(config: ASTConfig? = nil) throws -> Asteroid {
        switch (sharedInstance, config) {
        case let (i?, nil):
            return i
        case _ where sharedInstance != nil && config != nil:
            throw E.config(message: "You cannot change config after initialization!")
        case let (nil, c?):
            sharedInstance = Asteroid(config: c)
            return sharedInstance
        default:
            sharedInstance = Asteroid(config: "Default")
            return sharedInstance
        }
    }
}

let asteroidA = try! Asteroid.shared(config: "A")

asteroidA.config // A

let asteroidB = try! Asteroid.shared()

asteroidB.config // A

do {
    let asteroidC = try Asteroid.shared(config: "C")
} catch {
    print(error) // "config("You cannot change config after initialization!")\n"
}

//asteroidB.config = "B" // Error: Cannot assign to property: 'config' setter is inaccessible
Scott Gardner
  • 8,603
  • 1
  • 44
  • 36
  • Is this threadsafe? It seems that unless you have something like dispatchOnce protecting sharedInstance, then you haven't really got a singleton. – Confused Vorlon Nov 07 '16 at 11:55
  • @ConfusedVorlon From Apple's Swift 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." https://developer.apple.com/swift/blog/?id=7 – Scott Gardner Dec 04 '16 at 12:13
4

This seems to be the simplest way to implement the singleton in swift:

private let _AsteroidSharedInstance: Asteroid?

class Asteroid {
    var config: ASTConfig?

    class func setup(config: config) {
        _AsteroidSharedInstance = Asteroid(config: config)
    }

    class var sharedInstance: Asteroid {
        if _AsteroidSharedInstance == nil {
            println("error: shared called before setup")
        }

        return _AsteroidSharedInstance
    }

    init(config: config) {
        self.config = config
    }
}

with the usage:

Asteroid.sharedInstance()

Source and Source

The Tom
  • 2,790
  • 6
  • 29
  • 33
  • 1
    Oh I already know this (as you can see in my answer). But here my problem is that I have an init with a parameter. – knshn Feb 10 '15 at 12:08
  • Sorry about that, updated my answer to reflect your needs. This should work – The Tom Feb 10 '15 at 14:17
  • Well, say that I am going to use this class in another class. How do you pass the `config` object to the global private variable you are using on the top of the first solution? Or in the second one, there is no chance to give the `config` as a parameter to the computed `sharedInstance`. – knshn Feb 10 '15 at 15:16
  • Edited my answer again. I have no Xcode in front of me so I can't test if that would actually work – The Tom Feb 11 '15 at 07:34
  • Thank you for the effort. But when we use a global constant, it is default initialized and can't be changed. You can see if you have chance to put in XCode. – knshn Feb 11 '15 at 10:11
  • shouldn't the `init` be `private`? – juanreyesv Sep 16 '20 at 02:50
  • why do you use `class` instead of `static`? Do you plan to subclass your singleton? – Gargo Aug 24 '23 at 10:39
4

A simplified version of different answers; but without any force unwraps, not having shared as a func and having the possibility to have a config. It doesn't need to be more complicated then this, which works well with Swift 5:

import UIKit

final class ParameterSingleton {

  static var shared: ParameterSingleton {
    if let initializedShared = _shared {
      return initializedShared
    }
    fatalError("Singleton not yet initialized. Run setup(withConfig:) first")
  }
  private static var _shared: ParameterSingleton? // This can only be set by the setup() func since it is private
  private var config: ParameterSingletonConfig // The configuration for the singleton. Could be a `String` if so requested

  /// The ParameterSingleton setup func. Will initialize the singleton with the config. Without a config, `shared` will cause a `fatalError`
  ///
  /// - Parameter config: The config needed for initializing the singleton
  class func setup(withConfig config: ParameterSingletonConfig) {
    _shared = ParameterSingleton(withConfig: config)
  }


  // Make the init private so this class can only be used be invoking the `setup(withConfig:)` func
  private init(withConfig config: ParameterSingletonConfig) {
    self.config = config
  }

  /// The public func to do something
  func doSomething() {
    print("Config URL: \(config.url)")
  }
}

struct ParameterSingletonConfig {
  let url: String
}

//Will cause fatalError
ParameterSingleton.shared.doSomething()

//Will not cause fatalError
ParameterSingleton.setup(withConfig: ParameterSingletonConfig(url: "http://www.google.com"))
ParameterSingleton.shared.doSomething()

Of course, if you only need to set one parameter, you could remove ParameterSingletonConfig and replace it with a String instead

Paul Peelen
  • 10,073
  • 15
  • 85
  • 168
0
class Policies{
    static let shared = makeShared!();
    static var makeShared:(()->Policies)?;

    init(_ launchOptions:[UIApplicationLaunchOptionsKey:Any]?) {
        super.init();
        //initialization
    }
}
extension AppDelegate:UIApplicationDelegate{
    public func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool{
        Policies.makeShared = { Policies(launchOptions) }
    }
}
john07
  • 562
  • 6
  • 16
0
class Asteroid {
    private static var _shared: Asteroid!
    static var shared: Asteroid {
        _stared
    }

    private let config: ASTConfig?

    static func setup(config: ASTConfig) {
        _shared = Asteroid(config: config)
    }

    private init(config: ASTConfig) {
        self.config = config
    }
}

Note:

  1. everything belonging to Asteroid is declared inside Asteroid

  2. outer code sees only variable shared and func setup(config:), everything else is private like in a common Singleton pattern. In other words I hid everything should be hidden.

  3. no overengineering like dispatch_once, structs inside functions and etc. If you try access to shared it throws nil pointer exception (you can add your own errors throwing but it seems to be extra)

Gargo
  • 1,135
  • 1
  • 10
  • 21
  • One fatal flaw here. What happens if some code tries to access `Asteroid.shared` before any call to `Asteroid.setup`? The code crashes. – HangarRash Aug 24 '23 at 18:27
  • I already said - you will get a nil exception because you use it without of initialisation because you shouldn't call `setup` and `shared` in the correct order. And the accepted answer's code has the same behavior – Gargo Aug 25 '23 at 06:28