Singleton和init带参数

时间:2015-02-10 10:36:53

标签: swift

我想在我的类中使用单例模式,该模式具有带参数的私有init。它还有一个名为setup的类函数,用于配置和创建共享实例。我的Objective-c代码是:

@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

我被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
    }
}

我认为我仍然以客观的方式思考,并且无法用迅捷来解决这个问题。有什么帮助吗?

6 个答案:

答案 0 :(得分:23)

Objective-C代码的字面翻译可能是:

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
    }
}

或者,在Swift 1.2中,您可以消除Static结构并简化setup

private static var setupOnceToken: dispatch_once_t = 0

class func setup(config: ASTConfig) -> Asteroid {
    dispatch_once(&setupOnceToken) {
        _asteroidSharedInstance = Asteroid(config: config)
    }
    return _asteroidSharedInstance
}

这真的不是一个单身人士。 (我怀疑你知道这一点,但我提到了为了未来读者的利益)。通常,单个人可以在他们首次使用的任何地方和时间实例化。在这种情况下,它只在一个特定的地方进行实例化和配置,在尝试在其他地方使用之前必须注意这样做。这是一个非常奇怪的方法。我们失去了一些单例功能,但仍然受到所有传统的单例限制。

显然,如果你对此表示满意,那很好。但如果你有其他娱乐方式,那么两个人就会跳出来对我说:

  1. 创建这个真正的单例:你可以通过移动setup中的sharedInstance实例化来完成此任务(消除在使用ASTConfig之前必须调用init的依赖关系) setup方法。然后你可以退休class Asteroid { static let sharedInstance = Asteroid() private let config: ASTConfig init() { self.config = ASTConfig(...) } } 并像正常一样使用你的单身人士。最终的实现也大大简化了。它减少到类似的东西:

    ASTConfig

    显然,我怀疑魔鬼是在setup对象的细节中,但是如果你可以做一个正确的单例实现,你可以看到这更简单(特别是在Swift 1.2中)。以上消除了sharedInstance vs ASTConfig问题。消除私人全球。只是简单一点。

    话虽如此,我认为你有充足的理由按照你的方式去做。也许有一些关键原因导致必须setup对象传递给init方法而不是仅仅在Asteroid Asteroid内实例化它类。

    我只是觉得有必要指出一个合适的单身人士会更加优秀(更简单的实施和消除理论竞赛条件)。

  2. 完全放弃单身人士模式:假设使用正确的单身人士,如上所述,是不可能的,接下来的问题是你是否应该放弃任何剩余的单身人士的外表,只是实例化一个简单的setup你当前正在调用sharedInstance的地方,而不是依赖于setup,只需将其传递给真正需要它的对象。

    您已经指定您预先Asteroid {{1}}预先设定{{1}},所以让我们正式化这种关系并消除单身人士引入的许多结构性缺陷(见What's Alternative to Singleton或google"单身人士是邪恶的")。

  3. 不要误会我的意思。我认为你有充足的理由按照你的方式去做,如果当前的实施对你有用,那很好。但这是一种非常奇怪的方法,在这种方法中你不会受到单身人士的理论责任的影响而不享受所有的好处。

答案 1 :(得分:20)

我的解决方案略有不同。 这取决于

  1. 静态变量是懒洋洋地初始化
  2. 使用Config类存储初始化参数!
  3. 在init中使用fatalError强制执行安装调用(如果未首先调用安装调用)
  4. class MySingleton {
        static let shared = MySingleton()
    
        private class Config {
            var param:String?
        }
        private static let config = Config()
    
        class func setup(param:String){
            MySingleton.config.param = param
        }
    
        private init() {
            guard let param = MySingleton.config.param else {
                fatalError("Error - you must call setup before accessing MySingleton.shared")
            }
    
            //Regular initialisation using param
        }
    }
    

    要使用此功能,请使用

    进行设置
    MySingleton.setup(param:"MyParam")
    

    (显然,如果需要,可以通过扩展MySingleton.Config类和设置方法来使用多个参数)

    然后要访问单身,请使用

    MySingleton.shared
    

    我并不喜欢不得不使用单独的安装类,但我喜欢它与推荐的单例模式保持接近。将设置类保持在单例内可以使事情保持相当干净。

    注意 - 共享对象是单例。在后台,swift使用dispatchOnce来保证。但是,没有什么能阻止你使用不同线程的不同参数多次调用setup。

    目前,对共享的第一次调用将“锁定”设置。

    如果您想在第一次调用设置后锁定内容,请调用

    _ = MySingleton.shared
    

    在设置

    简单示例:

    class ServerSingleton {
        static let shared = ServerSingleton()
    
        private class Config {
            var host:String?
        }
        private static let config = Config()
    
        let host:String
    
        class func setup(host:String){
            ServerSingleton.config.host = host
        }
    
        private init() {
            guard let setupHost = ServerSingleton.config.host else {
                fatalError("Error - you must call setup before accessing MySingleton.shared")
            }
    
            host = setupHost
        }
    
        func helpAddress() -> String {
            return host+"/help.html"
        }
    }
    
    ServerSingleton.setup(host: "http://hobbyistsoftware.com")
    let helpAddress = ServerSingleton.shared.helpAddress()
    //helpAddress is now http://hobbyistsoftware.com/help.html
    

答案 2 :(得分:2)

这似乎是在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
    }
}

用法:

Asteroid.sharedInstance()

SourceSource

答案 3 :(得分:2)

您可以通过创建static sharedInstance属性private并使用方法返回现有实例(可选地更改其属性值)来定义最初采用一个或多个参数的单例,或初始化新实例并设置其属性值。例如,我也将您的config属性设为只读:

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

您可以将config属性设置为private,将private(set) var config: ASTConfig? 属性设为只读...

shared(config:)

... shared(config:)的来电者仍然可以更改配置。为了防止这种情况发生,您需要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 投掷方法:

onclick

答案 4 :(得分:0)

mObsInt.set(mObsInt.get()+1);

答案 5 :(得分:0)

不同答案的简化版本;但没有任何强行包装,没有将<app-async-complete>作为shared,并且有可能拥有func。不需要那么复杂,它可以与Swift 5很好地配合:

config

当然,如果只需要设置一个 参数,则可以删除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() 并用ParameterSingletonConfig代替