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
У меня немного другое решение. Это зависит от
- Статическая переменная лениво инициализируется
- Использование структуры Config для хранения параметров инициализации
- Обеспечение вызова установки с помощью 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