Being Lazy in Swift

Lazy Variables & Constants in Swift

Say you’re working on some application that has optional features - perhaps you have an image that CAN be resized if a thumbnail is required, but there is a chance that a thumbnail will never be viewed. Image scaling algorithms are computationally taxing - what if our user never ends up accessing a thumbnail? In that case, it doesn’t seem right to demand that a user ALWAYS wait for such an operation to be performed. Remember, allocation is slow, so defer until needed, and memory is limited, so don’t waste it. Use lazy instantiating when the initial value for a property isn’t known until after object initialization, when performing some intense algorithm or perhaps when calculating a large number (like pi to a large number of digits). 

In comes lazy loading - this is a technique used to defer instantiation of a variable or sequence (in swift) until it is actively needed. In swift, if you have a variable in a class that is part of the class’ designated initializer, the value must be computed since the compiler forces that every property within an initializer method be initialized. That may look something like this:

extension UIImage {

    fun resizedTo(size: CGSize) -> UIImage {

        // insert algorithm here    

    }

}

Class UserImage {

    static let smallSize = CGSize(width:36, height: 36)

    var thumbnail: UIImage

    var defaultImage: UIImage

    init (defaultImage: UIImage) {
        self.defaultImage = defaultImage

        self.thumbnail = defaultImage.resizedTo(UserImage.smallSize)

    }

}

What if we never need to use the thumbnail? We just performed an intensive operation for no reason at all - lazy variables take away this problem. In Objc we could leave the thumbnail out of the initializer and do a check for its value by making the thumbnail a computed property with set and get values. In the get, if the value is nil, THEN we would call our resizedTo method; in the set, simply give it a new value. We can immediately set a thumbnail value if we wish to, but if we end up attempting to access it without having initially set a value, it will call our resizing method. Thankfully, lazy instantiation with swift allows us to write less code while still achieving the same result.

The above can be achieved with just a single line of code- 

lazy var thumbnail: UIImage = self.defaultImage.resizedTo(UserImage.smallSize)

If we need to add more lines of code to compute our property, we can just use a closure - 

lazy var thumbnail: UIImage = { 

    let size = CGSize(

        width:min(UserImage.smallSize.width, self.defaultImage.size.width), 

        height: min(UserImage.smallSize.height, self.defaultImage.size.height)

    return self.defaultImage.resizedTo(size) 

} ()

The fact that a property is lazy means the value will only be computed when the entire class has been initialized, so it is safe to reference self within a computed lazy property. 

Note that let constants, if declared at global scope or as a type property (static let) are automatically lazy. Other than that, you can’t make them lazy. An interesting thing to note, as stated by Chris Lattner (designer and project leader of LLVM, Clang and Swift), is that “let” is about physical immutability, not logical immutability. Physical immutability means that once the bits are set in memory (when the let value is initialized), they can’t be changed.

Lazy Sequences in Swift

The SequenceType and CollectionType protocols in Swift have a computed property named lazy - it returns a LazySequence or LazyCollection, depending on which protocol we’re implementing. Lazy applies only to high-order functions in Swift (array.lazy.map(addOneFunction)). This code would only execute for values we actually need; if we printed (array[0], array[10], array[20]) and lazily mapped our function to those values, our program would only make three calculations, one for each value we try to access. If our array has 20,000 elements in it, we can use this to avoid calculating the values of each element after mapping a function to it, and limit our processing to just the values we want.