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.
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
varkeyword), 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
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.
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 a
lazy property, we can declare
lazy to get the desired result of suspending a task.
It is clear from the above example that unlike before
heavyComputation 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.
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
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.
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
lazymodifier 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
Happy Coding 💻