Ghostboard pixel

Properties in Swift

Properties in Swift

There are two types of properties in Swift: stored properties and computed properties. Stored properties store values (constant or variable) as part of an instance or type, whereas computed properties don’t have a stored value.

Hint: This post has been updated to Swift 4 

[toc]

Video


Stored properties

Let’s start by looking at stored properties. Imagine you have a class named circle:

class Circle {
    
    var radius: Double = 0
    
}

let circle = Circle()
circle.radius = 10

print("radius: \(circle.radius)") //radius: 10.0

Circle has an instance variable called radius  with a default value of 0. In Swift, every instance variable is automatically a property. So now you have the possibility to add so called property observers. In Swift there a two types of property observers: One is called before the new value is assigned and the other one afterwards.

The property observer, which is called after the assignment, is marked with the keyword didSet. In our example, you can use it for example to check the new assigned value:

class Circle {
    
    var radius: Double = 0 {
        didSet {
            if radius < 0 {
                radius = oldValue
            }
        }
    }
    
}

let circle = Circle()

circle.radius = -10
print("radius: \(circle.radius)") //radius: 0.0

circle.radius = 10
print("radius: \(circle.radius)") //radius: 10.0

In the property observer you can access the old value of the property through the variable oldValue.

You can also use the property observer willSet, which is called before the new value is assigned:

class Circle {
    
    var radius: Double = 0 {
        willSet {
            print("About to assign the new value \(newValue)")
        }
        didSet {
            if radius < 0 {
                radius = oldValue
            }
        }
    }
    
}

let circle = Circle()

circle.radius = 10 //About to assign the new value 10.0

In willSet  you have access to the new value of the property through the variable newValue.

Computed Properties

Contrary to stored properties, computed properties don’t have a stored value. So each time you call a computed property, the value needs to be calculated. In the class Circle  you can define the property area as a computed property:

class Circle {
    
    var radius: Double = 0 {
        didSet {
            if radius < 0 {
                radius = oldValue
            }
        }
    }
    
    var area: Double {
        get {
            return radius * radius * Double.pi
        }
    }

}

let circle = Circle()
circle.radius = 5

print("area: \(circle.area)") //area: 78.5398163397448

A computed property always needs a getter. If it lacks a setter, the property is called a read-only property. In our example, there is a good application for a setter though:

import Foundation

class Circle {
    
    var radius: Double = 0 {
        didSet {
            if radius < 0 {
                radius = oldValue
            }
        }
    }
    
    var area: Double {
        get {
            return radius * radius * Double.pi
        }
        set(newArea) {
            radius = sqrt(newArea / Double.pi)
        }
    }
    
}

let circle = Circle()

circle.area = 25

print("radius: \(circle.radius)") //radius: 2.82094791773878

Now, after assigning a new value to area , radius  will be calculated.

Initilization of stored properties

Every stored property has to have a value after its instance has been created. There are two ways:

  • initialize the value inside the init method
  • set a default value for the property

The following example covers both cases:

class Circle {
    
    var radius: Double
    var identifier: Int = 0
    
    init(radius: Double) {
        self.radius = radius
    }
    
}

var circle = Circle(radius: 5)

If a stored property doesn’t have a value after its instance has been created, the code will not compile.

Lazy Properties

If a stored property with a default value is marked with the keyword lazy, its default value will not be initialized immediately, but when the property is called for the very first time.

So if the property gets never called, it will never be initialized. You could use this feature for example, if the initialization is expensive in terms of memory or CPU usage.

Let’s take a look at the following (very simple) example:

class TestClass {
    
    lazy var testString: String = "TestString"
    
}

let testClass = TestClass()
print(testClass.testString) //TestString

The property will not initalized until it gets accessed. In this example this is not very obivious though. But since the initialization can also happen inside a block, we can make it a little bit more obvious:

class TestClass {
    
    lazy var testString: String = {
        print("about to initialize the property")
        return "TestString"
    }()
    
}

let testClass = TestClass()
print("before first call")
print(testClass.testString)
print(testClass.testString)

The output of this example is:

before first call
about to initialize the property
TestString
TestString

So that means that the block is only called once – when the property is accessed for the very first time. Since this is stored property is variable, the initial value can be changed afterwards.

Type Properties

Type properties are part of an type but not of an instance – also know as static properties. Both stored and computed properties can be type properties. For that, you use the keyword static:

class TestClass {
    
    static var testString: String = "TestString"
    
}

print("\(TestClass.testString)") //TestString

As you can see, they are accessed by using the type name but not an instance. Furthermore, type properties always need a default value because there is not initialization.

Public Properties With Private Setters

As I’ve pointed out in more details in this post, it’s a common scenario that you don’t want to provide a public getter, but a private setter. This is a basic principle of encapsulation. By doing this, only the class itself can manipulate that property, but can still be accessed and read from outside of the class.

Take a look at the following example:

public class Circle {
    
    public private(set) var area: Double = 0
    public private(set) var diameter: Double = 0
    
    public var radius: Double {
        didSet {
            calculateFigures()
        }
    }
    
    public init(radius:Double) {
        self.radius = radius
        calculateFigures()
    }
    
    private func calculateFigures() {
        area = Double.pi * radius * radius
        diameter = 2 * Double.pi * radius
    }
}

let circle = Circle(radius: 5)

print("area: \(circle.area)") //area: 78.5398163397448
print("diameter: \(circle.diameter)") //diameter: 31.4159265358979

circle.area = 10 //Compiler Error: cannot assign to property: 'area' setter is inaccessible

Here the properties area and diameter can be read from outside of the class, but only be set inside the class. For that you have to use the combination public private(set). In my experience this feature is very rarely used in iOS development, but it’s very useful to create code, that has fewer bugs.

References

Title Image: @ Fabrik Bilder / shutterstock.com