“Lazy” keyword — Swift 5 (iOS)

What is lazy? When to use it? Is it safe?

Ayush Singhi
5 min readOct 25, 2020
Source — Self😎

Hello, there!

Many of you must have seen a lazy keyword in your codebase or may have used it, but what does it mean? What does it do? Is it always safe to do things lazily? Let’s see.

Lazy allows you to do quite a few things—

  1. Instantiate a code entity — class, struct, etc. without giving initial values to some or all of its members at the time of instantiation.
  2. Suspend any task until needed.
  3. Store the computed values and prevent re-computations.

Declaring lazy

A member can be declared lazy by simply adding a lazy keyword to its signature. A lazy member should always be declared as a var. Apple says —

You must always declare a lazy property as a variable (with the var keyword), because its initial value might not be retrieved until after instance initialization completes.

And since all constants must have a value at the end of initialization, it is necessary that lazy properties be declared as a var.

fullName — a lazy member declared as var

Let us now consider an example to understand what is meant by not having a value at the time of instantiation.

A User class with a simple lazy property to simulate a heavy computational task (inefficient)

In the above example, we have a class User with few data members and an initializer init(::). When we try to create user1 the system gives an error that the property fullName has no value. While at the same time another property heavyComputation is declared as lazy and it does not result in any kind of an error.

So, it is clear that by declaring properties lazy we can avoid the initialization constraints. This leads us to the other aspect of lazy— Suspension of a task.

Suspend task

Another good reason to use lazy is to make initialization faster by suspending tasks at the time of initialization. In other words, as devs, we want to be able to instantiate our entity as quickly as possible. So, any heavy-duty task or computations that can be avoided, must be avoided until they are actually needed. Let’s take a look at a few examples.

All the heavy computation is done at the time of initialization

As is evident from the above example, all the computations are done at the time of initialization. This happens because heavyComputation is declared as a let constant which needs to have a concrete value at the end of initialization. Therefore, it is computed at the same time. But this is not always desirable.

As we already know at the time of initialization it is not necessary to have a value assigned to alazy property, we can declare heavyComputation as lazy to get the desired result of suspending a task.

No computations are performed and the user is initialized

It is clear from the above example that unlike beforeheavyComputation is not evaluated at the time of initialization because heavyComputation is now a lazy variable. In fact a value for heavyComputation will not be calculated until the first time it is accessed.

Avoid re-computations

Based on what we have seen till now one may argue that the above behavior— non-compulsion of values, suspending the task can be easily emulated by using a computed property. This is where we are headed next. This shall become clear from the example below.

heavyComputation is a computed property

It is evident from this example, that computed property can also suspend a task.

But does this mean that computed property and lazy are similar? No! At first, they may seem similar but they have a very basic difference which makes lazy all the more useful and interesting.

Let’s try to access our computed property multiple times and see what happens.

Calling the property heavyComputation 2 times

What we find above is that when we access the computed property — heavyComputation multiple times, it is actually computed each time it is accessed (here twice).

Let’s see what happens when we make heavyComputation a lazy property and try to access it as we did above.

A lazy property heavyComputation is accessed 2 times

Interestingly, this time the property is computed only once. This means that after the value is computed the first time, it is being saved for future use and is not actually being computed repeatedly unlike the case with computed property.

Hence, on the basis of the above examples, we can easily establish that lazy properties store values and are not computed each time they are accessed. Thus, saving an ample amount of processing power and time.

When not to use lazy

Having understood the working of lazy, it is now imperative that we understand its usage and be cautious while using it.

Here is the catch, since lazy is calculated only once we cannot use it to compute values that depend on other variables whose values change more frequently. This is because we may end up with the wrong value in the lazy variable if any of the depending values change after its first computation.

Additionally, lazy is not thread-safe. This means we may end up with different values for the same variable when accessed on different threads. Apple says —

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

This shows that too much use of lazy may actually give irregular and wrong results. But having said these, there are still times and places where we may want to do things lazily. Since UI always renders on the main thread this makes it a good potential candidate for lazy loading, however, we need to be very careful while doing that. Also, huge computation or builders can be carried out lazily.

This brings us to the end of this article.

Thank you for following along. I hope you enjoyed reading it and found it informative. Do share your views about what do you think are the best/worst places to use lazy.

Happy Coding 💻

--

--

Ayush Singhi
Ayush Singhi

No responses yet