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