创建一个通用的单例模式

6
这有点让我头疼。基本上我想要有两个不同的单例,它们都继承自同一个类。在任何一个单例中,我都想使用某个类,该类本身是派生的。所以我有一个Utility,并且有AUtil:UtilityBUtil:Utility。还有一个Singleton,分别使用AUtilityBUtility作为ASingleton的背景。我在所有方面都失败了。最后一次尝试是一个工厂模式,但只会让Swift 1.2崩溃:
protocol Initializable { init() }
class A:Initializable {
  var x = "A"
  required init() {}
}
class B:Initializable {
  var x = "B"
  required init() {}
}

class C {
  let t:Initializable
  init(t:Initializable) {
    self.t = t
    println(t)
  }
  func factory() {
    println(t.dynamicType())
  }
}

就像我所说的那样,我还尝试将以下模式设置为通用模式:

private let _SingletonSharedInstance = StaticClass()

class StaticClass {
  class var sharedInstance : StaticClass {
    return _SingletonSharedInstance
  }
}

let s = StaticClass.sharedInstance

这个句子并不是通用的,但是我的所有尝试都失败了,所以我展示了我的起点。

无论如何,我似乎陷入了末日和死亡之间的迷失中。

2个回答

6
你的意思是类似这样吗?
protocol Initializable: class { init() }

private var instances = [String: Initializable]()

func singletonInstance<T: Initializable>(_ ty: T.Type = T.self) -> T {
    let name = NSStringFromClass(ty)
    if let o = (instances[name] as? T) {
        return o
    }
    let o = ty()
    instances[name] = o
    return o
}

例如,它的一个用户方面。
class Foo: Initializable { required init() {} }
class Bar: Initializable { required init() {} }

let foo1 = singletonInstance() as Foo // or `singletonInstance(Foo.self)`
let foo2 = singletonInstance() as Foo
assert(foo1 === foo2)

let bar1 = singletonInstance() as Bar
let bar2 = singletonInstance() as Bar
assert(bar1 === bar2)

我已经测试了上述代码并且在Swift 1.2中成功运行。


看起来很有前途。谢谢 :-) 我会尝试今天让它工作。 - qwerty_so
使用flatMap的原因是什么?似乎if let o = (instances[name] as? T )也可以工作。我不得不查找这个新方法,因为它是最新的1.2 beta版本之一的新方法。 - qwerty_so
@ThomasKilian 对不起,你是对的。我之前不知道 if let 可以解包一个双重包装的可选值 Initializable??。谢谢! - findall
此外,请注意我上面的代码不是线程安全的,在多线程中没有显式同步会导致竞态条件。 - findall
由于单例是在初始化期间分配的,所以这对我来说不是一个问题 :-) - qwerty_so
哇,这段代码太棒了!我一直在尝试创建一个通用的单例类,但是泛型中不支持存储属性。:/ 有没有可能修改这段代码,使得初始化看起来像这样:let foo = singletonInstance(Foo) ?? 那就太好了。如果有人能向我展示关于Swift中“T.Type”和“T.self”的解释的好页面,我将不胜感激。 - DevAndArtist

3

受到findalls实现的启发,我构建了自己的单例生成器,它略微更强大一些。

在Swift中,您可以创建任何类或结构体类型的单例。唯一需要做的事情就是将其中一个不同的协议实现到您的类型中,并使用Swift 2.0或更高版本。

public protocol SingletonType { init() }

private var singletonInstances = [String: SingletonType]()

extension SingletonType {

    // this will crash Xcode atm. it's a Swift 2.0 beta bug. Bug-ID: 21850697
    public static var singleton: Self { return singleton { $0 } }

    public static func singleton(setter: (_: Self) -> Self) -> Self {

        guard let instance = singletonInstances["\(self)"] as? Self else {

            return setInstance(self.init(), withSetter: setter, overridable: true)
        }

        return setInstance(instance, withSetter: setter, overridable: false)
    }

    private static func setInstance(var instance: Self, withSetter setter: (_: Self) -> Self, overridable: Bool) -> Self {

        instance = restoreInstanceIfNeeded(instance1: instance, instance2: setter(instance), overridable: overridable)

        singletonInstances["\(self)"] = instance

        return instance
    }

    private static func restoreInstanceIfNeeded(instance1 i1: Self, instance2 i2: Self, overridable: Bool) -> Self {
        // will work if the bug in Swift 2.0 beta is fixed !!! Bug-ID: 21850627
        guard i1.dynamicType is AnyClass else { return i2 }

        return ((i1 as! AnyObject) !== (i2 as! AnyObject)) && !overridable ? i1 : i2
    }
}

这段代码看起来有点吓人,但不要害怕。协议扩展中的公共函数将为您创建两个访问点。

例如,现在您可以编写以下代码:

// extend your type: as an example I will extend 'Int' here
extension Int : SingletonType {} // nothing else to do, because Int already has an 'init()' initializer by default

// let the magic happen

Int.singleton // this will generate a singleton Int with 0 as default value
Int.singleton { (_) -> Int in 100 } // should set your Int singleton to 100
Int.singleton { $0 - 55 } // your singleton should be 45 now

// I need to mention that Xcode will produce the setter like this and trow an error
Int.singleton { (yourCustomInstanceName) -> Self in // replace 'Self' with 'Int' and you should be fine
    return yourCustomInstanceName
}

// btw. we just ignored the return value everywhere
print(Int.singleton) // will print 45 here
var singleton2 = Int.singleton { $0 + 5 }

singleton2 += 10 

print(Int.singleton) // should print 50, because 'singleton2' is just a copy of an Int value type 

class A : SingletonType { 
    var name = "no name"
    required init() {}
}

A.singleton { $0; let i = A(); i.name = "hello world"; return i } // custom init on first singleton call for type A
print(A.singleton.name)
print(A.singleton { $0.name = "A"; return $0 }.name)
print(A.singleton.name) 
// should print "hello world" and twice the string "A"

如果您有任何想法来增强这段代码并使其更加安全,请告诉我。我很快会将这段代码推到GitHub上(MIT许可证),这样每个人都可以从中受益。
更新:我稍微修改了一下代码,所以现在当第一次调用setter函数时,您可以传递一个自定义初始化的类实例。
更新2:我删除了ClassInstance协议并修改了私有恢复函数。Instance协议现在被称为SingletonType。setter函数不再是可选的。现在Xcode 7 beta 3在调用getter时会崩溃并提供一个illegal instruction: 4错误。但这是一个已确认的beta bug。

1
“setInstnace” 可能是一个拼写错误,您可以进行更正 :-) - qwerty_so
我在一个通用类中使用了你的代码来进行缓存管理。效果非常好! - Jan ATAC
@CanATAC,这段代码有点过时了。如果您感兴趣,可以在GitHub上查看我的最终版本:https://github.com/DevAndArtist/SingletonGenerator 很高兴能帮助到别人。 - DevAndArtist

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接