Learn how to use lazy properties in Swift to improve performance, avoid optionals or just to make the init process more clean.


According to wikipedia:

In computer programming, lazy initialization is the tactic of delaying the creation of an object, the calculation of a value, or some other expensive process until the first time it is needed.

That little quote pretty much sums up everything, however because we're working with the Swift programming language, we have a thing called optionals. If you don't know what are those, please read the linked articles first, and come back afterwards. 🤐


The ultimate guide of being lazy

When a property is only needed at some point in time, you can prefix it with the lazy keyword so it'll be "excluded" from the initialization process and it's default value will be assigned on-demand. This can be useful for types that are expensive to create, or needs more time to be created. Here is a quick tale of a lazy princess. 👸💤  

class SleepingBeauty {

    init() {
        print("zzz...sleeping...")
        sleep(2)
        print("sleeping beauty is ready!")
    }
}

class Castle {

    var princess = SleepingBeauty()

    init() {
        print("castle is ready!")
    }
}

print("a new castle...")
let castle = Castle()

The output of this code snippet is something like below, but as you can see the princess is sleeping for a very long time, she is also "blocking" the castle. 🏰

a new castle...
zzz...sleeping...
sleeping beauty is ready!
castle is ready!

Now, we can speed things up by adding the lazy keword, so our hero will have time to slay the dragon and our princess can sleep in her bed until she's needed... 🐉 🗡 🤴

class SleepingBeauty {
    
    init() {
        print("zzz...sleeping...")
        sleep(2)
        print("sleeping beauty is ready!")
    }
}

class Castle {
    
    lazy var princess = SleepingBeauty()
    
    init() {
        print("castle is ready!")
    }
}

print("a new castle...")
let castle = Castle()
castle.princess

Much better! Now the castle is instantly ready for the battle, so the prince can wake up his loved one and... they lived happily ever after. End of story. 👸 ❤️ 🤴

a new castle...
castle is ready!
zzz...sleeping...
sleeping beauty is ready!

I hope you enjoyed the fairy tale, but let's do some real coding! 🤓


Avoiding optionals with lazyness

As you've seen in the previous example lazy properties can be used to improve the performance of your Swift code. Also you can eliminate optionals in your objects. This can be useful if you're dealing with UIView derived classes. For example if you need a UILabel for your view hierarchy you usually have to declare that property as optional or as an implicitly unwrapped optional stored property. Let's remake this example by using lazy & eliminating the need of the evil optional requirement. 😈

class ViewController: UIViewController {
    
    lazy var label: UILabel = UILabel(frame: .zero)
    
    override func loadView() {
        super.loadView()
        
        self.view.addSubview(self.label)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        self.label.textColor = .black
        self.label.font = UIFont.systemFont(ofSize: 16, weight: .bold)
    }
}

It isn't so bad, however I still prefer to declare my views as implicitly unwrapped optionals. Maybe I'll change my mind later on, but old habits die hard... 💀


Using a lazy closure

You can use a lazy closure to wrap some of your code inside it. The main advantage of being lazy - over stored properties - is that your block will be executed ONLY if a read operation happens on that variable. You can also populate the value of a lazy property with a regular stored proeprty. Let's see this in practice.

class ViewController: UIViewController {
    
    lazy var label: UILabel = {
        let label = UILabel(frame: .zero)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.textColor = .black
        label.font = UIFont.systemFont(ofSize: 16, weight: .bold)
        return label
    }()
}

This one is a nice practice if you'd like to declutter your init method. You can put all the object customization logic inside a closure.  The closure executes itself on read (self-executing closure), so when you call self.label your block will be executed and voilá: your view will be ready to use.

NOTE: that you can't use self in stored properties, but you are allowed to do so with lazy blocks. Be careful: you should always use [unowned self], if you don't want to create reference cycles and memory leaks.  ♻️


Lazy initialization using factories

I already have a couple of articles about factories in Swift, so now i just want to show you how to use a factory method & a static factory combined with a lazy property.

Factory method

If you don't like self-executing closures, you can move out your code into a factory method and use that one with your lazy variable. It's simple like this:

class ViewController: UIViewController {
    
    lazy var label: UILabel = self.createCustomLabel()

    private func createCustomLabel() -> UILabel {
        print("called")
        let label = UILabel(frame: .zero)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.textColor = .black
        label.font = UIFont.systemFont(ofSize: 16, weight: .bold)
        return label
    }
}

Now the factory method works like a private initializer for your lazy property. Let's bring this one step further, so we can improve reusability a little bit...

Static factory

Outsourcing your lazy initializer code into a static factory can be a good practice if you'd like to reuse that code in multiple parts of your application. For example this is a good fit for initializing custom views. Also creating a custom view is not really a view controller task, so the responsibilities in this example are more separated.

class ViewController: UIViewController {
    
    lazy var label: UILabel = UILabel.createCustomLabel()
}

extension UILabel {

    static func createCustomLabel() -> UILabel {
        let label = UILabel(frame: .zero)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.textColor = .black
        label.font = UIFont.systemFont(ofSize: 16, weight: .bold)
        return label
    }
}

As a gratis you can enjoy the advantages of static factory properties / methods, like caching or returning specific subtypes. Pretty neat! 👍


Conclusion

Lazy variables are a really convenient way to optimize your code, however they can only used on structs and classes. You can't use them as computed properties, this means they won't return the closure block every time you are trying to access them.

Another important thing is that lazy properties are not thread safe, so you have to be careful with them. Plus you don't always want to eliminate implicitly unwrapped optional values, sometimes it's just way better to simply crash! 🐛

Don't be lazy!

...but feel free to use lazy properties whenever you can! 😉


External sources