Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trigger lazy initializer again in Swift by setting property to nil

I want a lazily-initialized property whose initializer I can invoke again if I set the property to nil.

If I define my property this way:

lazy var object = { /*init code*/ }()

...and later invoke the property, the initializer is triggered once. However, if I set object to nil later in my program, the initializer is not invoked again. How can I do that in Swift?

I looked into computed properties but they don't actually store values, so whenever I invoke the variable, the computation or initialization always occurs. I want to compute only whenever the property is nil.

like image 639
MLQ Avatar asked Sep 10 '14 07:09

MLQ


1 Answers

The lazy property initializer is responsible of initializing the property the first time it is accessed in read mode. Setting to nil has no effect on the initialization status - it's just a valid value the property stores.

You can mimic a lazy initialization with 3 properties:

  • a private initializer, implemented as a computed property (or a closure if you prefer)
  • a private backing property, storing the actual value
  • a non private property, which is the one you actually use in your code

The code looks like this:

class MyClass {
    private var _myPropInitializer: Int {
        return 5
    }

    private var _myProp: Int?

    var myProp: Int? {
        get {
            if self._myProp == nil {
                self._myProp = self._myPropInitializer
            }
            return _myProp!
        }
        set {
            _myProp = newValue
        }
    }
}
  • the initializer property returns a computed value for the variable when it needs to be initialized, which is the 5 integer in the above example
  • myProp is an optional integer (to be able to store a nil):
    • on set, it will store the new value in the _myProp property
    • on get, if _myProp is nil, it invokes the initializer, assigning it to _myProp, and it returns its value

If you want to reuse that pattern, it's better to put everything in a class:

class Lazy<T> {
    private let _initializer: () -> T
    private var _value: T?
    var value: T? {
        get {
            if self._value == nil {
                self._value = self._initializer()
            }
            return self._value
        }
        set {
            self._value = newValue
        }
    }

    required init(initializer: () -> T) {
        self._initializer = initializer
    }
}

Note: a struct is not usable because setting a property inside a property getter is not allowed, whereas in a class it is.

Then you can use it as follows:

class MyTestClass {
    var lazyProp: Lazy<Int>

    init() {
        self.lazyProp = Lazy( { return 5 } )
    }
}

Some tests in playground:

var x = MyTestClass()
x.lazyProp.value // Prints {Some 5}
x.lazyProp.value = nil
x.lazyProp._value // Prints nil
x.lazyProp.value // Prints {Some 5}

The downside is that you have to access to the actual property as x.lazyProp.value and not as x.lazyProp.

like image 88
Antonio Avatar answered Oct 02 '22 16:10

Antonio