2015-02-10 42 views
14

我想在我的班级中使用单身模式,其中带有参数的私人init。它还具有一个名为setup的类函数,它可以配置和创建共享实例。我的Objective-C代码将是:带参数的单身人士和init

@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的方式思考和无法迅速弄清楚。任何帮助?

回答

18

逐字翻译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 
    } 
} 

或者,在雨燕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对象的细节,但如果你可以做一个合适的单身实现,你可以看到这是更简单(ESP 。在Swift 1.2中)。而上述消除setup vs sharedInstance的问题。消除私人全球。一直更简单。

    说了这么多之后,我假设你有很强的理由像你这样做。也许有一些关键的原因,你为什么要必须通过ASTConfig反对setup方法,而不是只是在Asteroid类的init实例化它。

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

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

    您已经指定了你手动setupAsteroid往上走前,让我们正式确定这种关系,并消除了许多单身引入结构性缺陷(见What's Alternative to Singleton或谷歌“单身是邪恶”)的。

不要误解我的意思。我假设你有充足的理由按照你的方式去做,如果当前的实现对你有用,那没问题。但这是一个非常好奇的方法,在这种方法中,你不必享受所有的好处就会陷入单身人士的理论责任。

1

这似乎是实现迅速单身的最简单的方法:

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

与用法:你的Objective-的

Asteroid.sharedInstance() 

SourceSource

+1

哦,我已经知道这个(你可以在我的答案见)。但在这里我的问题是,我有一个参数的初始化。 – knshn 2015-02-10 12:08:06

+0

对不起,我的答案更新以反映您的需求。这应该工作 – 2015-02-10 14:17:19

+0

好吧,说我要在另一个班级使用这个班级。你如何将'config'对象传递给你在第一个解决方案顶部使用的全局私有变量?或者在第二个中,没有机会将'config'作为计算的'sharedInstance'的参数。 – knshn 2015-02-10 15:16:09

1

您可以通过使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 
+0

这是线程安全吗?看起来,除非你有像dispatchOnce保护sharedInstance这样的东西,那么你并没有真正拥有一个单身人士。 – 2016-11-07 11:55:05

+0

@ConfusedVorlon从Apple的Swift博客:“全局变量的惰性初始化器(也适用于结构体和枚举的静态成员)在第一次访问全局时运行,并作为dispatch_once启动以确保初始化是原子性的。这使得在你的代码中使用dispatch_once成为一种很酷的方式:只需用一个初始化程序声明一个全局变量并将其标记为私有。“ https://developer.apple.com/swift/blog/?id=7 – 2016-12-04 12:13:20

8

我有一个稍微不同的解决方案。 这依赖于

  1. 静态变量是懒洋洋地初始化
  2. 使用第二类存储初始化PARAMS!
  3. 中强制初始化与fatalError设置通话(如果设置呼叫没有首先调用)

    private class SingletonSetupHelper { 
        var param:String? 
    } 
    
    class MySingleton { 
        static let shared = MySingleton() 
        private static let setup = SingletonSetupHelper() 
    
        class func setup(param:String){ 
         MySingleton.setup.param = param 
        } 
    
        private init() { 
         let param = MySingleton.setup.param 
         guard param != nil else { 
          fatalError("Error - you must call setup before accessing MySingleton.shared") 
         } 
    
         //Regular initialisation using param 
        } 
    } 
    

要使用此,您可以将它与

MySingleton.setup(param:"MyParam") 

然后访问单身人士,您使用

MySingleton.shared 

我并不是想要使用单独的安装类,但我喜欢这样保持接近推荐的单例模式。

注 - 共享对象是一个单身人士。在后台,swift使用dispatchOnce来保证。然而,没有什么能阻止你用不同线程的不同参数多次调用设置。

目前,共享的第一个电话会“锁定”设置。

如果你想第一次调用设置之后锁定下来,然后只需调用

_ = MySingleton.shared 

在设置