cfischer
cfischer

Reputation: 24902

Faulting or "lazy initialisation" pattern in Swift

I commonly use a "faulting" or lazy initialization pattern in iOS when dealing with big objects.

Whenever a class has a property pointing to a "fat" object, I create a custom getter that checks if the iVar is nil. If it is, it creates the fat object and returns it. If it isn't, it just returns the "fat" object.

The container of this property also subscribes to memory warnings, and when one is received, it sets the iVar to nil, reducing the memory footprint. As you see, it's pretty similar to faulting in Core Data.

I'm trying to reproduce this in Swift, but haven't found a decent and elegant solution so far.

a) First Attempt: lazy stored properties

This doesn't work, because if I set the property to nil, it will remain nil forever. The "magic" only happens the first time you access the property:

struct FatThing{
    // I represent something big, which might have to be
    // "faulted" (set to nil) when a memory warning 
    // is received

    var bigThing = "I'm fat"
}



class Container {
    lazy var fat: FatThing? = FatThing()
}



var c = Container()
c.fat
c.fat = nil
c.fat   // returns nil

b) Second attempt: A stored property with observers

This also fails, because of the lack of get observers. I need a willGet and didGet, not just willSet and didSet.

Why on Earth are there no get obeservers? What's the use of this half backed thing called observers?

c) Third attempt: A computed property with a stored helper property

This is the only working option I've found so far, but it's as ugly as a baboon's rear end!

struct FatThing{
    // I represent something big, which might have to be
    // "faulted" (set to nil) when a memory warning 
    // is received
    var bigThing = "I'm fat"
}



class Container {

    private var _fat : FatThing? // having this extra and exposed var kills my inner child
    var fat: FatThing? {
        get{
            if _fat == nil {
                _fat = FatThing()
            }
            return _fat
        }
        set{
            _fat = newValue
        }
    }
}

var c = Container()
c.fat
c.fat = nil
c.fat   // returns FatThing

So much for making my code look simpler and shorter...

Is there a simple and elegant way to implement this? This is no exotic stuff in a memory deprived environment as iOS!

Upvotes: 2

Views: 701

Answers (1)

Antonio
Antonio

Reputation: 72760

The ability to override the getter or setter individually is a peculiarity of objective C that has no counterpart in swift.

The right option at your disposal is no. 3, using a backing property to store the fat data and a computed property to make it accessible. I agree that there's some boilerplate code, but that's the tradeoff for having what you need.

However, if you use that pattern frequently, then you can create a protocol:

protocol Instantiable {
    init()
}

and implement it in your FatThing struct/class. Next, create a generic function containing the boilerplate code:

func lazyInitializer<T: Instantiable>(inout property: T?) -> T {
    if property == nil {
        property = T()
    }

    return property!
}

Note that T must implement Instantiable - that allows you to create an instance with a parameterless constructor.

Last, use it as follows:

private var _fat : FatThing? // having this extra and exposed var kills my inner child
var fat: FatThing? {
    get { return lazyInitializer(&self._fat) }
    set { _fat = newValue }
}

Note that in your code you don't have to declare the fat computer property as optional - you are ensuring in the get implementation that it is always not nil, so a better implementation is:

var fat: FatThing {
    get{
        if _fat == nil {
            _fat = FatThing()
        }
        return _fat!
    }
    set{
        _fat = newValue
    }
}

Upvotes: 3

Related Questions