“Lazy” keyword — Swift 5 (iOS)
What is lazy? When to use it? Is it safe?
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—
- Instantiate a code entity — class, struct, etc. without giving initial values to some or all of its members at the time of instantiation.
- Suspend any task until needed.
- 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
.
Let us now consider an example to understand what is meant by not having a value at the time of instantiation.
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.
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.
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.
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.
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.
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 💻