Singleton和init带参数

kns*_*shn 22 swift

我想在我的类中使用具有private initwith参数的单例模式.它还有一个类函数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
Run Code Online (Sandbox Code Playgroud)

我被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
    }
}
Run Code Online (Sandbox Code Playgroud)

我想我仍然以客观方式思考,并且无法用swift来解决这个问题.有帮助吗?

Rob*_*Rob 24

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
    }
}
Run Code Online (Sandbox Code Playgroud)

或者,在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
}
Run Code Online (Sandbox Code Playgroud)

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

显然,如果你对此感到满意,那很好.但如果你有其他选择,那么两个人就会跳出来:

  1. 制作这个真正的单例:你可以通过移动方法内部的实例化来完成这个(消除在setup使用之前必须调用的依赖性sharedInstance).然后你可以退休,只需像平常一样使用你的单身人士.最终的实现也大大简化了.它减少到如下:ASTConfiginitsetup

    class Asteroid {
        static let sharedInstance = Asteroid()
    
        private let config: ASTConfig
    
        init() {
            self.config = ASTConfig(...)
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

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

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

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

    你已经指定了你要手动setupAsteroid前面,让我们正式确立这种关系,并消除了许多单身引入结构性缺陷的(见什么是替代单身或谷歌"单身是邪恶的").

别误会我的意思.我假设你有令人信服的理由按照你的方式去做,如果当前的实现适合你,那很好.但这是一种非常奇怪的方法,在这种方法中,你无法享受单身人士的理论责任而不享受所有的好处.


Con*_*lon 23

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

  1. 静态变量是懒惰的初始化
  2. 使用Config类存储初始化参数!
  3. 在init中使用fatalError强制执行安装调用(如果未首先调用安装调用)

.

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
    }
}
Run Code Online (Sandbox Code Playgroud)

要使用它,请使用它进行设置

MySingleton.setup(MySingleton.Config(param: "Some Param"))
Run Code Online (Sandbox Code Playgroud)

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

然后访问单身,你使用

MySingleton.shared
Run Code Online (Sandbox Code Playgroud)

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

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

目前,对共享的第一次调用将"锁定"设置.

如果您想在第一次调用设置后锁定内容,那么只需调用即可

_ = MySingleton.shared
Run Code Online (Sandbox Code Playgroud)

在设置中

简单示例:

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
Run Code Online (Sandbox Code Playgroud)


Sco*_*ner 5

您可以定义一个单例,该单例最初采用一个或多个参数,方法是创建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
Run Code Online (Sandbox Code Playgroud)

您可以通过将其设置器定义为...来使您的config属性变为只读。private

private(set) var config: ASTConfig?
Run Code Online (Sandbox Code Playgroud)

...但是调用者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
Run Code Online (Sandbox Code Playgroud)