lazy Property in Swift


Object creation is a heavy process. When we create a class object, all the public and private properties of that class are initialised inside the constructor. Every variable inside a class initialisation requires a certain amount of time to allocate the memory on the heap and hold its reference on the stack. The more variables, the more time it may take but since the time is in microseconds or even less, it's not observable.

Sometimes we don't need all the objects to be initialised during the class object creation itself.
There can be two reasons for that.
  1. That object/property/variable is dependent on another object to initialise first and use its reference.
  2. The flow is such that we need that object only in a certain condition.
In Swift, we have certain features which can help us in delaying that object initialisation as per the requirement.

One way of doing that is using lazy initialization.
Lazy initialisation is also denoted as lazy var in Swift.

lazy initialisation is a delegation of object creation when the first time that object will be called. The reference will be created but the object will not be created. The object will only be created when the first time that object will be accessed and every next time the same reference will be used.

The basic initialisation of lazy property in a User struct object by using lazy will look like below
  
struct User {
let name : String
let age : Int
}

struct Department {
var users : [User]

init(users : [User]) {
print("Department constructor is called")
self.users = users
}

lazy var youngestUser : User? = {
print("Department youngestUser is computed")
return self.users.min(by: {$0.age < $1.age})
}()
}

Let's try to understand what exactly lazy is


Lazy initialisation is a delegation of property initialisation that will only be called when that property will be used the first time.
Even if we initialise the struct or class object, the lazy property will not be set.

Lets see this with an example.
  
struct User {
let name : String
let age : Int
}

struct Department {
var users : [User]

init(users : [User]) {
print("Department constructor is called")
self.users = users
}

lazy var youngestUser : User? = {
print("Department youngestUser is computed")
return self.users.min(by: {$0.age < $1.age})
}()
}

var engineeringDepartment = Department(users: [
User(name: "Suneet", age: 29),
User(name: "Ballu", age: 20),
User(name: "Agrawal", age: 50)
])

//This will print: Department constructor is called

print(engineeringDepartment.youngestUser ?? "")

//This will print: Department youngestUser is computed
//followed by : User(name: "Ballu", age: 20)
As we can see the lazy property was not initialised even when we initialised the Department struct object. That only got initialised when we called the lazy property for the first time.

Things to keep in mind while working with lazy property

Can only be used with var

lazy can only be used with var but it can't be used with a let (immutable) property.
  
struct Department {
//...

lazy let youngestUser : User? = { //'lazy' cannot be used on a let
return self.users.min(by: {$0.age < $1.age})
}()
}

Can be of primitive or non-primitive type

A lazy property can be of primitive or non-primitive type.
  
struct Department {
//...

lazy var youngestUserAge : Int? = {
return self.users.min(by: {$0.age < $1.age})?.age
}()

lazy var youngestUser : User? = {
return self.users.min(by: {$0.age < $1.age})
}()
}

Can only be used inside a class or struct

Lazy can only be used inside a class or struct. We can't define it in a function or at the root level of a playground file.
  
lazy var youngestUser : Int = { //Lazy is only valid for members of a struct or class
return 1
}()

The struct or class object accessing lazy property should also be var

The object which is trying to access the lazy property should also be mutable means var.
  
struct Department {
//...

lazy var youngestUser : User? = {
print("Department youngestUser is computed")
return self.users.min(by: {$0.age < $1.age})
}()
}

let engineeringDepartment = Department(users: [
User(name: "Suneet", age: 29),
User(name: "Ballu", age: 20),
User(name: "Agrawal", age: 50)
])

print(engineeringDepartment.youngestUser ?? "")
//Cannot use mutating getter on immutable value: 'engineeringDepartment' is a 'let' constant
Please note that it's not compulsory to create all the objects of that class or struct should be var but in order to access the lazy property, the variable should be var (mutable) type.

Once the value is computed, it will not change until the constructor is called again

The computation of the lazy property will only happen once. Later the same values will be returned without computation.
  
var engineeringDepartment = Department(users: [
User(name: "Suneet", age: 29),
User(name: "Ballu", age: 20),
User(name: "Agrawal", age: 50)
])

print(engineeringDepartment.youngestUser ?? "")
//This will print: User(name: "Ballu", age: 20)

engineeringDepartment.users.append(User(name: "John", age: 18))
//or
engineeringDepartment.users = [User(name: "John", age: 18)]

print(engineeringDepartment.youngestUser ?? "")
//This will still print: User(name: "Ballu", age: 20)
As we can see, even if changed values in the users array or even initialised the array again, the lazy property is still holding the last values.
The lazy property will only be computed again when the Department constructor will be called on same object else the lazy property will hold the same values.


Few things to remember about lazy property

A lazy property is used to delay the initialisation of property and it will be initialised only when the first time that property will be called but it will only be called once. Later it will return the same values.
Also, it can only be used inside a class or struct and the calling object should also be var.