What Is the Advantage of a Lazy Var in Swift

What is the advantage of a lazy var in Swift

Lazy Stored Property vs Stored Property

There are a few advantages in having a lazy property instead of a stored property.

  1. The closure associated to the lazy property is executed only if you read that property. So if for some reason that property is not used (maybe because of some decision of the user) you avoid unnecessary allocation and computation.
  2. You can populate a lazy property with the value of a stored property.
  3. You can use self inside the closure of a lazy property

Swift - Lazy Var vs. Let when creating views programmatically (saving memory)

Whether you will use lazy var or not depends on your code and its context. It is not bad or good on its own. You have to decide when it is appropriate.

Before you can decide that, you have to know what lazy var is.

What is lazy var?

Lazy initialization is a concept where initialization (construction) of variable content is delayed until its first usage. First access to such variable triggers initialization. Since content is not created until variable is used (needed) using lazy initialized variables can save resources.

That is primary drive behind lazy initialization. You don't create something until you need it. That is also logic you will use when deciding whether something should be lazy var or not.

If you are dealing with views (or anything else) that are always visible (needed) there is little point in using lazy initialization. On the other hand when you are dealing with instances that are not always needed - then using lazy var is justified.

If your view is always visible in presented view controller, you will not accomplish much by making it lazy. If it is visible only under specific circumstances - for instance when user expands some collapsed panel - then making it lazy makes sense. It will make your view controller load faster and use less memory by default.


As far as thread safety is concerned, lazy var are not thread safe in Swift.

That means if two different threads try to access the same lazy var at the same time, before such variable has been initialized it is possible that one of the threads will access partially constructed instance.

You can find more about thread safety in:

Swift - is lazy var thread-safe?

Make "lazy var" threadsafe

Swift's Lazy Var

In your examples the outcomes are not quite the same, in the following ways:

  1. Single instantiation. Each time buttonPressed() is called, a new HeavyClass will be instantiated. This is not the case when using the lazy keyword, which will only create the instance on first access. To match the lazy semantics you would have to check and set if heavyClass == nil before each access.

  2. Nullability. You must unwrap your heavyClass each time you want to use it, either through optional chaining (heavyClass?.doStuff()) or force unwrapping (heavyClass!.doStuff()). You are also able to set the variable back to nil, which would be a compiler error in the first example.

The real wins of lazy variables are when you have multiple places which use the variable. I'm sure you can spot the repetition here:

func buttonPressed() {
if self.heavyClass == nil {
self.heavyClass = HeavyClass()
}
self.heavyClass?.doStuff()
}

func aDifferentButtonPressed() {
if self.heavyClass == nil {
self.heavyClass = HeavyClass()
}
self.heavyClass?.doSomethingElse()
}

This is tidied up using a lazy variable:

func buttonPressed() {
self.heavyClass.doStuff()
}

func aDifferentButtonPressed() {
self.heavyClass.doSomethingElse()
}

Difference between Lazy var and var as-a-closure in Swift

The difference is when the init code for the variable is run. For lazy vars, the init code is run on first access of that variable. For non-lazy vars, it's run when the struct/class is initialized.

struct N {
lazy var a: Int = { print("Setting A"); return 5}();
var b: Int = { print("Setting B"); return 5 }()
}

var n = N()
print(n.a)
print(n.b)

Output:

Setting B
Setting A
5
5

Note how non-lazy b is initialized first. a is only initialized when it's accessed. In either case, the initializer for each property is only run once.

Why and when to use lazy with Array in Swift?

lazy changes the way the array is processed. When lazy is not used, filter processes the entire array and stores the results into a new array. When lazy is used, the values in the sequence or collection are produced on demand from the downstream functions. The values are not stored in an array; they are just produced when needed.

Consider this modified example in which I've used reduce instead of count so that we can print out what is happening:

Not using lazy:

In this case, all items will be filtered first before anything is counted.

[1, 2, 3, -1, -2].filter({ print("filtered one"); return $0 > 0 })
.reduce(0) { (total, elem) -> Int in print("counted one"); return total + 1 }
filtered one
filtered one
filtered one
filtered one
filtered one
counted one
counted one
counted one

Using lazy:

In this case, reduce is asking for an item to count, and filter will work until it finds one, then reduce will ask for another and filter will work until it finds another.

[1, 2, 3, -1, -2].lazy.filter({ print("filtered one"); return $0 > 0 })
.reduce(0) { (total, elem) -> Int in print("counted one"); return total + 1 }
filtered one
counted one
filtered one
counted one
filtered one
counted one
filtered one
filtered one

When to use lazy:

option-clicking on lazy gives this explanation:

pop-up for lazy in Xcode

From the Discussion for lazy:

Use the lazy property when chaining operations:

  1. to prevent intermediate operations from allocating storage

    or

  2. when you only need a part of the final collection to avoid unnecessary computation

    I would add a third:

  3. when you want the downstream processes to get started sooner and not have to wait for the upstream processes to do all of their work first

So, for example, you'd want to use lazy before filter if you were searching for the first positive Int, because the search would stop as soon as you found one and it would save filter from having to filter the whole array and it would save having to allocate space for the filtered array.

For the 3rd point, imagine you have a program that is displaying prime numbers in the range 1...10_000_000 using filter on that range. You would rather show the primes as you found them than having to wait to compute them all before showing anything.

Swift lazy var reference cycle

The reason that there isn't is a reference cycle is because the closure isn't stored on a. If a stored the closure and the closure contained self then there would be a reference cycle.

For lazy properties, everything after the = isn't called until the first time you access property2. When you do, the closure is created, called, freed from memory, and then the value is returned to you. This is why you can get rid of the self. in the closure since it will never capture self.

Lazy Initialization sounds and works great. Why not always default to using lazy initialization?

Consider this example of a lazy var:

struct S {
lazy var lazyVar: Int = { /* compute some value */ 0 }()
}

What really happens behind the scenes is something like this:

struct S {
var _lazyVar: Int? = nil

var lazyVar: Int {
mutating get {
if let existingValue = _lazyVar {
return existingValue
}
else {
let newlyComputedValue = /* compute some value */ 0
_lazyVar = newlyComputedValue
return newlyComputedValue
}
}
}
}

As you see, every access of lazyVar requires a branch, to check if there's a value already, or if it's necessary to compute one for the first time. This adds overhead that easily outweighs the benefit of lazily evaluating values with simple (fast) derivations.

Role of Swift 3 lazy var with no getter

lazy means that the initial value of stored property is evaluated and assigned on the first access (so this is actually unrelated to getters
or setters which are used with computed properties).

lazy var toto = ... some expression evaluating to an `Int` ...

defines a lazy Int property.

lazy can be used to delay the evaluation of the initial value
until the property is needed, e.g. if that evaluation is
"expensive" or has side effects.

But from the language/compiler point of view, the initial value on the right-hand side
can be an arbitrary expression.
It does not matter if it is a constant or a "complicated" expression involving function calls.

lazy var a = 5 // A constant
lazy var b = someFunctionReturningAnInt() // Function call
lazy var c = { _ -> Int in
return 42
}() // Immediately evaluated closure

are all valid definitions. There is no advantage of using lazy with a constant initial value, but it is valid Swift.

Swift function vs lazy var vs computed property - difference?

  • lazy vars are actually stored properties, so you can't put it in extensions or anywhere stored properties are not allowed.
  • The getter for computed properties is run every time you refer to that property. This can be significant especially if the getter is time-consuming or has side-effects to other parts of the code.
  • The getter for lazy vars are only run when the property is first referred to and never again.
  • lazy vars are variables. You can mutate them.
  • Computed properties can optionally have a setter, so sometimes they are read-only.
  • Using a function like that is very similar to a read only computed property. You just have to add () when getting its value.

When to use ?, !, None, or Lazy?

Not exactly that.

All variables must be initialised before the first use, and all class/struct stored properties must be assigned value in respective initialiser. Optionals are not about being allowed uninitalised at some point, but about being allowed to contain no value, which is represented by nil, which is still perfectly an initialised stated for such variable. Therefore, if something can not be known at the moment of initialisation then that's probably where you will use some sort of an optional (e.g. delegate for a view).

Implicitly unwrapped optionals is a sort of shorthand for cases when a variable might be empty, but we are absolutely sure that when we will be really using it it will hold an actual value (typical example is a property in a view controller that holds reference to a view).

Forced unwrapping does not convert optional into implicitly unwrapped optional, instead it gives you a value that is there if it's there (i.e. if optional is not nil), and throws an exception if it's not.

Lazy properties are used in cases when you want to defer their initialisation to a later stage, when the property is actually being used for first time. Usual case is if you need to access an expensive resource to do that (load huge file from disk, download it via network, etc), especially so if there might be cases when such property is not going to be used at all (why loading it from the disk if we will not use it probably?).



Related Topics



Leave a reply



Submit