Singleton и init с параметром

Я хочу использовать шаблон singleton в моем классе, который имеет частный 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
    }
}

Я думаю, что я все еще думаю в objective-c и не мог понять это быстро. Любая помощь?

Ответы

Ответ 1

Дословный перевод вашего кода 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
}

Это действительно не синглтон. (Я подозреваю, что вы это знаете, но я упоминаю это для будущих читателей). Обычно синглтоны могут быть созданы каждый раз, когда и когда они впервые используются. Это сценарий, в котором он создается и настраивается только в одном конкретном месте, и вы должны позаботиться об этом, прежде чем пытаться использовать его в другом месте. Это очень любопытный подход. Мы теряем некоторые сингл-функциональные возможности, но все еще страдаем всеми традиционными синглтонными ограничениями.

Ясно, если вы в порядке с этим, это прекрасно. Но если вы развлекаете альтернативы, два выпрыгивают на меня:

  • Сделайте этот реальный синглтон: вы можете это сделать (исключая зависимость вызова setup перед тем, как использовать sharedInstance), перемещая экземпляр ASTConfig внутри метода init. Затем вы можете уйти в отставку setup и просто использовать свой синглтон, как обычно. Результирующая реализация также значительно упрощена. Он сводится к чему-то вроде:

    class Asteroid {
        static let sharedInstance = Asteroid()
    
        private let config: ASTConfig
    
        init() {
            self.config = ASTConfig(...)
        }
    }
    

    Ясно, что я подозреваю, что дьявол находится в деталях этого объекта ASTConfig, но если вы можете сделать правильную реализацию singleton, как вы можете видеть, это намного проще (особенно в Swift 1.2). И выше исключается проблема setup vs sharedInstance. Исключает частный глобальный. Просто проще всего.

    Сказав это, я предполагаю, что у вас были веские причины сделать это так, как вы. Возможно, есть какая-то важная причина, по которой вы должны передать объект ASTConfig в метод setup, а не просто создавать его самостоятельно в init класса Asteroid.

    Мне просто хотелось указать, что подходящий синглтон будет очень предпочтительным (как более простая реализация, так и устранение теоретических условий гонки).

  • Исключить одноэлементный шаблон целиком. Предполагая, что использование подходящего синглтона, как описано выше, невозможно, следующий вопрос: стоит ли просто отказаться от любого остального подобия одного синглета, просто создайте простой Asteroid, где вы в настоящее время вызывают setup, а затем вместо того, чтобы полагаться на sharedInstance, просто передайте его объектам, которые действительно нуждаются в нем.

    Вы уже указали, что вы собираетесь вручную setup Asteroid спереди, поэтому давайте формализовать эти отношения и устранить многие структурные недостатки, которые вводят синглтоны (см. Что альтернатива Singleton или google "одиночные игры - зло" ).

Не поймите меня неправильно. Я предполагаю, что у вас есть веские причины сделать это так, как вы, и если текущая реализация работает для вас, это прекрасно. Но это очень любопытный подход, в котором вы обременены теоретической ответственностью одиночных игроков, не пользуясь всеми преимуществами.

Ответ 2

У меня немного другое решение. Это зависит от

  1. Статическая переменная лениво инициализируется
  2. Использование структуры Config для хранения параметров инициализации
  3. Обеспечение вызова установки с помощью fatalError в init (если вызов установки не вызывается до доступа к синглтону)

,

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

Чтобы использовать это, вы устанавливаете это с

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

(Очевидно, что при необходимости вы можете использовать несколько параметров, разворачивая структуру MySingleton.Config)

Затем для доступа к синглтону, вы используете

MySingleton.shared

Я не в восторге от необходимости использовать отдельную структуру установки, но мне нравится, что она остается близкой к рекомендованному шаблону синглтона. Хранение структуры установки внутри синглтона обеспечивает чистоту.

Примечание - общий объект является одноэлементным. В фоновом режиме swift использует dispatchOnce, чтобы гарантировать это. Однако ничто не мешает вам вызывать setup несколько раз с разными конфигами из разных потоков.

На данный момент первый звонок в общий доступ заблокирует настройку.

Если вы хотите заблокировать вещи после первого вызова установки, просто позвоните

_ = MySingleton.shared

в настройке

Простой пример:

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

Ответ 3

Это, по-видимому, самый простой способ реализовать singleton в 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()

Источник и Источник

Ответ 4

Вы можете определить одноэлемент, который сначала берет один или несколько параметров, создавая свойство 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

Ответ 5

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

Ответ 6

Упрощенная версия разных ответов; но без каких-либо сил разворачивается, не 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