Blog / July 13, 2018 / 5 mins read / By Suneet Agrawal

Computed 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 computed properties.

Computed properties are the properties that don’t get initialised while object creation or constructor is called. They get computed every time the property is accessed.

We can use it for any heavy computation which we want to do on a conditional basis. Unlike Lazy properties, these properties get computed every time the property is accessed.

The basic initialisation of a computed property in a `User class object by 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
    }
    
    var youngestUser: User? {
        print("Department youngestUser is computed")
        return self.users.min(by: {$0.age < $1.age})
    }
}

Let’s try to understand what exactly computed property is

A Computed property is a delegation of property initialisation that will only be called when that property will be used. This will compute the property every time this property is accessed.

A Computed property is similar to a normal function which will return a value of certain type but it can’t accept parameters.

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
    }
    
    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 computed property was not initialised even when we initialised the `Department class object. That only got initialised when we called the computed property.

Things to keep in mind while working with computed property

Can only be a var type

caomputed property can only be var but it can't a let (immutable) property.

class Department {
   //...
    
    let youngestUser : User? {      <e>//'let' declarations cannot be computed properties</e>
        return self.users.min(by: {$0.age < $1.age})
    }
}

Can be of primitive or non-primitive type

A computed property can be of primitive or non-primitive type.

class Department {
    //...
    
   var youngestUserAge : Int? {
        return self.users.min(by: {$0.age < $1.age})?.age
    }
    
    var youngestUser : User? {
        return self.users.min(by: {$0.age < $1.age})
    }
}

Can be used inside or outside a class or struct.

A computed property can be used outside a class or struct also.

var pi : Float {
    return 22/7
}

print(pi)

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

The object which is trying to access the computed property should also be mutable means `var.

class Department {
   //...
    
    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 ?? "") 
//error: Cannot assign to property: '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 computed property, the variable should be var (mutable) type.

Can also be defined in extensions

A computed property can also be defined in an extension of the class.

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

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

Can also be overridden

A computed property can also be overridden in a class by adding `override to the property in the extending class.

class Department {
    var users : [User]
    
    init(users : [User]) {
        print("Department constructor is called")
        self.users = users
    }
    
    var youngestUser : User? {
        print("Department youngestUser is computed")
        return self.users.min(by: {$0.age < $1.age})
    }
}

class SubDeppartment : Department {
    override var youngestUser : User? {
        print("SubDeppartment youngestUser is computed")
        return self.users.max(by: {$0.age < $1.age})
    }
}

The value will be computed every time when the property will be accessed

The computation of the computed property will only every time when the property will be accessed.

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 print: User(name: "John", age: 18)

Can add get wrapper also to the property getter but that is optional

We can add a get wrapper also to the property’s getter but that is optional. It’s not recommended.

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

Is get-only

Unlike the stored properties, We can’t assign a value to a computed property as they are get-only properties.

engineeringDepartment.youngestUser = User(name: "John", age: 18) <e>//cannot assign to property: 'youngestUser' is a get-only property</e>

Few things to remember about computed property

A computed property is used to delay the initialisation of property and it will be computed every time when that property will be called. This is similar to a function only but it doesn’t take any parameter.

Also, it can be used inside or ouside a class or struct or even in extensions and the calling object should also be `var.

Comments