Creating a generic singleton

2019-01-23 19:53发布

问题:

This is a bit of a head banger (for me). Basically I want to have 2 different singletons that inherit from the same class. In either I want to use a certain class which itself is derived. So I have Utility and both AUtil:Utility and BUtil:Utility. And Singleton that is used as ASingleton using AUtility in its stomach and B respectively. I failed on all frontiers. The last attempt was a factory pattern which simply got Swift 1.2 to Segfault:

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

As said I also tried to make the following pattern generic:

private let _SingletonSharedInstance = StaticClass()

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

let s = StaticClass.sharedInstance

(This one isn't generic as you see. But all my attempts failed and so I show my starting point.)

Anyway I seem to be lost between doom and death.

回答1:

Do you mean something like this?

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
}

An use-side of it, for instance.

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)

(I've tested the code above and got it to work in Swift 1.2.)



回答2:

Inspired by findalls implementation, I build my own singleton generator, which is a little more powerful.

You can create a singleton of any Class or Structure type in Swift. The only thing you have to do is to implement one of two different protocols to your type and use Swift 2.0 or newer.

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

This may look a little scary, but don't be afraid of this code. The public function inside the protocol extension will create two access points for you. For example you will be able to write code like this now:

// 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"

If you have any idea how to enhance this code and make it even safer, please let me know. I will push this code on GitHub (MIT License) soon, so everyone can benefit from it.

UPDATE: I modified the code a little so you can now pass a custom initialized instance of a class with the setter function when its called the first time.

UPDATE 2: I removed ClassInstance protocol and modified the private restore function. The Instance protocol is now called SingletonType. The setter function is not optional anymore. Right now Xcode 7 beta 3 will crash and provide an illegal instruction: 4 error when you will call the getter. But this is a confirmed beta bug.