swift 使用参数进行单例和初始化

lokaqttq  于 2023-02-11  发布在  Swift
关注(0)|答案(6)|浏览(213)

我想在我的类中使用单例模式,它有一个带参数的私有init,还有一个名为setup的类函数,用于配置和创建共享示例,我的目标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

我被斯威夫特困住了:

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

我想我还在用objective-c的方式思考,用swift也想不出来。有什么帮助吗?

ckocjqey

ckocjqey1#

我有一个稍微不同的解决方案。它依赖于
1.静态变量被延迟初始化
1.使用Config结构存储初始化参数
1.在init中使用fatalError强制执行setup调用(如果在访问单例之前没有调用setup调用)

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

要使用此功能,请使用以下命令对其进行设置

MySingleton.setup(MySingleton.Config(param: "Some Param"))

(显然,如果需要,可以通过扩展MySingleton.config结构使用多个参数)
然后要访问单例对象,可以使用

MySingleton.shared

我并不热衷于使用单独的setup结构体,但我喜欢它保持接近推荐的单例模式。将setup结构体保持在单例中可以保持相当干净。
注意-共享对象是一个单例对象。在后台,swift使用dispatchOnce来保证这一点。但是,没有什么可以阻止您使用来自不同线程的不同配置多次调用setup。
目前,第一次调用shared将"锁定"设置。
如果您想在第一次调用setup后锁定所有内容,则只需调用

_ = MySingleton.shared

设置中

    • 简单示例:**
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
o4hqfura

o4hqfura2#

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.使其成为真实的的单例:你可以通过将ASTConfig的示例化移到init方法中来完成这一点(消除了在使用sharedInstance之前必须调用setup的依赖性)。然后你可以退出setup,像往常一样使用你的单例。最终的实现也得到了极大的简化。它被简化为如下所示:

class Asteroid {
    static let sharedInstance = Asteroid()

    private let config: ASTConfig

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

很明显,我怀疑问题出在ASTConfig对象的细节上,但是如果你能做一个合适的单例实现,你会发现这要简单得多(特别是在Swift 1.2中)。上面的内容消除了setupsharedInstance的问题。消除了私有全局。只是简单了很多。
话虽如此,我认为您有令人信服的理由这样做,也许有一些关键的理由,为什么您必须将ASTConfig对象传递给setup方法,而不是自己在Asteroid类的init中示例化它。
我只是觉得有必要指出,一个合适的单例将是非常可取的(既简化了实现,又消除了理论上的竞态条件)。
1.完全放弃单例模式:假设使用正确的单例(如上所述)是不可能的,那么下一个问题是是否应该放弃单例的任何剩余外观,只是在当前调用setup的地方示例化一个简单的Asteroid,然后不依赖于sharedInstance,而是将它传递给真正需要它的对象。
你已经预先指定了你将要手动setupAsteroid,所以让我们正式化这种关系,并消除许多单例引入的结构缺陷(参见What's Alternative to Singleton或google“singletons are evil”)。
不要误解我的意思。我假设您有令人信服的理由来这样做,如果当前的实现对您有效,那也没关系。但这是一种非常奇怪的方法,在这种方法中,您会被单例的理论责任所拖累,而不会享受到所有的好处。

rsl1atfo

rsl1atfo3#

你可以定义一个单例,它最初带有一个或多个参数,方法是创建static sharedInstance属性private并使用一个方法返回现有示例(可选地更改其属性值),或者初始化一个新示例并设置其属性值。

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属性的setter定义为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
a14dhokn

a14dhokn4#

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

mrwjdhj3

mrwjdhj35#

不同答案的简化版本;但是没有任何强制展开,shared不是func,而是有可能是config。它不需要比这个更复杂,它在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()

当然,如果您只需要设置 * 一个 * 参数,那么您可以删除ParameterSingletonConfig并将其替换为String

a2mppw5e

a2mppw5e6#

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

相关问题