4

I realize that static variables are implicitly lazy, which is really great. Doing the below will not create the instance until it's first called:

static var test = Test()

However, assigning a new instance to the static variable initializes the original, then assigns the new instance which is troubling for me:

SomeType.test = AnotherTest() //Initializes Test then AnotherTest type

To give more context on what I'm trying to do, I'm trying to setup a pure Swift dependency injection using this article. It's not working so well when swapping the types out in my unit tests because the original type always gets initialized when assigning the mock type.

Here's a more fuller, playground sample:

protocol MyProtocol { }

class MyClass: MyProtocol {
    init() { print("MyClass.init") }
}

////

struct MyMap {
    static var prop1: MyProtocol = MyClass()
}

protocol MyInject {

}

extension MyInject {
    var prop1: MyProtocol { return MyMap.prop1 }
}

////

class MyMock: MyProtocol {
    init() { print("MyMock.init") }
}

// Swapping types underneath first initializes
// original type, then mock type :(
MyMap.prop1 = MyMock()

prints: MyClass.init
prints: MyMock.init

How can I make MyMap.prop1 = MyMock() not first initialize the original MyClass first?

Hamish
  • 78,605
  • 19
  • 187
  • 280
TruMan1
  • 33,665
  • 59
  • 184
  • 335

1 Answers1

3

You need lazy loading. Try this:

struct MyMap {
    private static var _prop1: MyProtocol?
    static var prop1: MyProtocol {
        get { return _prop1 ?? MyClass() }
        set(value) { _prop1 = value }
    }
}

Or this:

struct MyMap {
    private static var _prop1: MyProtocol?
    static var prop1: MyProtocol {
        get {
            if _prop1 == nil {
                _prop1 = MyClass()
            }
            return _prop1!
        }
        set(value) { _prop1 = value }
    }
}
ObjectAlchemist
  • 1,109
  • 1
  • 9
  • 18
  • Whoa, why does this work??!.. Is this a bug in Swift or was this intentional? – TruMan1 Apr 12 '17 at 19:05
  • 1
    Its as intended by swift. A var needs a value as long as it exists. This value may be nil or a concrete one. Your var can not be nil by your definition, therefore you have to specify a value (or compile throw an error). It doesn't matter if you call the get or set. Swift has to ensure that your var has a value before working with it. Thats what apple call "swift is save". – ObjectAlchemist Apr 12 '17 at 19:08
  • @codealchimist But `static` stored properties are lazily loaded – it shouldn't *have* to call the property initialiser if it's first assigned to before being loaded. But that's just currently how Swift behaves. – Hamish Apr 12 '17 at 19:17
  • 1
    It's also worth noting that `static` stored property initialisers are thread safe, whereas lazily loading like in your second example is not thread safe. – Hamish Apr 12 '17 at 19:21
  • @Hamish True both comments. There was a bug report for it too, maybe it will change in future swift versions and static isn't lazy anymore or it works as expected from a lazy var. Think it will be the first one for compatibility reasons. We will see.. – ObjectAlchemist Apr 12 '17 at 19:28
  • Thx @Hamish for pointing out the thread-safety. I wrapped the getter in a serial queue and worked great. I was able to prove that it worked by calling `MyMap.prop1` with `DispatchQueue.concurrentPerform(iterations: 1000)`. – TruMan1 Apr 12 '17 at 21:44
  • For reference: ``` get { guard _prop1 == nil else { return _prop1! }; queue.sync { if _prop1 == nil { _prop1 = MyClass() } }; return _prop1! } ``` – TruMan1 Apr 12 '17 at 21:47