Search code examples
swiftlazy-initialization

Is backed property safer than (lazy) in Swift?


Is it safer to use computed property backed by another property, than to use lazy modifier in Swift?

// method 1: using LAZY variable
lazy var myVar:Int {
    // assume that this calculation takes
    // a lot of time and resources
    return 5
}

Now consider the following quote from Apple's Swift 2 (Pre-release):

NOTE

If a property marked with the lazy modifier is accessed by multiple threads simultaneously and the property has not yet been initialized, there is no guarantee that the property will be initialized only once.

Personally, I have been doing my own "lazy" logic like this:

Consider the alternative approach:

// method 2: using calculated property backed by another property
var myVar:Int? {
    if self._myVar != nil {
        return self._myVar
    }
    let aVar:Int = 5   // assume this is done after exhaustive calcuation
    self._myVar = aVar
    return self._myVar
}

var _myVar:Int? = nil

Using (method 2), do I get guarantee that the "exhaustive calculation" will only be executed once?


Solution

  • Your proposed method 2 does not guarantee anything that lazy does not.

    // method 2: using calculated property backed by another property
    var myVar:Int? {
        if self._myVar != nil {
            return self._myVar
        }
        let aVar:Int = 5   // assume this is done after exhaustive calcuation
        self._myVar = aVar
        return self._myVar
    }
    
    var _myVar:Int? = nil
    

    Moreover, it has the added pitfall of being an optional rather than a non-optional. This would be slightly better as an implicitly unwrapped optional so we don't have to continuously unwrap it.

    And, we can't set myVar. Plus _myVar should be private.

    But the problem is that you're not locking the initialization code down to be synchronized.

    Suppose you have thread A start to access myVar. The if self._myVar != nil check returns false, so we're not returning self._myVar. Now we enter the exhaustive calculation.

    Now before exhaustive calculation completes, Thread B tries accessing myVar. Because Thread A has not completed exhaustive calculation yet, the if self._myVar != nil still returns false, and now Thread B does the exhaustive calculation.

    Essentially, this is this the same problem that the lazy keyword gives you.... but you wrote a whole lot of code to do it that way when lazy would have sufficed.