Swift Properties
Swift Properties are broadly classified into two types.
- Stored Properties: store constants and variables and are provided by classes and structures
- Computed Properties: Instead of storing values, these calculated values. Are provided by class structs as well as enumerations.
[quote font=”helvetica”]Note: A computed property can’t be a constant.[/quote]
Swift Stored Property Example
An example of Stored Properties is given below.
import Foundation struct Rect { var length: Int let breadth: Int } var r = Rect(length: 5, breadth: 5) r.length = 6
Both the length and breadth are stored properties (variable and constant respectively) in the above snippet.
Modifying a Constant Structure instance
let r = Rect(length: 5, breadth: 5) r.length = 6 //Error: Cannot assign to property: 'r' is a 'let' constant
If the struct is initialized as a constant, changing stored properties isn’t possible even though they’re declared as variables. The reason is that structs are value types. The same isn’t the case with classes since they’re reference types.
Swift Lazy Properties
As per Apple’s documentation Swift Lazy property is defined as:
[quote font=”helvetica”]A Lazy stored property is a property whose initial values are not calculated until the first time it’s used. You can indicate a laze stored property by writing the lazy modifier before its declaration.[/quote]
Unlike other properties, a lazy property is initialized right before it’s being accessed for the first time.
- Lazy properties are useful when preventing unnecessary object creation and subsequently saving memory.
- Typically a property is defined as lazy when it’s dependent on other properties that are not known yet.
- A lazy modifier cannot be added to a constant. Constants require an initial value before the initialization completes which is not the case with lazy properties.
- A lazy property must have an initializer and cannot be used simply with any variable.
The following is a wrong usage case of lazy var.
import Foundation struct Rect { lazy var length: Int //Error: Lazy properties must have an initializer let breadth: Int }
An example of lazy var is given below:
import Foundation struct Rect {
var length: Int let breadth: Int init(length: Int, breadth: Int) { print("Rect struct is initialised now from the lazy var property") self.length = length self.breadth = breadth } } struct Square {
var sidesEqual: Bool lazy var r = Rect(length: 6, breadth: 6) init(sidesEqual: Bool) { self.sidesEqual = sidesEqual } } var s = Square(sidesEqual: false) if s.sidesEqual { print(s.r.length) } else { print("Rect struct hasn't been initialised using the lazy var") //this gets printed } var s1 = Square(sidesEqual: true) if s1.sidesEqual { print(s1.r.length) //prints Rect struct is initialised now from the lazy var property \n 6 } else { print("Rect struct hasn't been initialized using the lazy var") //not printed }
It’s evident in the above code that the Rect struct is instantiated from the lazy var instance only when it meets the condition. Lazy Properties are handy when your code has many object instances since it’ll create the declared instances only when they’re needed.
Once a lazy property is initialized, for further accesses it generally reuses the first instance.
[quote font=”helvetica”]Note: If a property marked with the lazy modifier is accessed by multiple threads simultaneously and the property has not yet been initialized, there is no guarantee that the property will be initialized only once.[/quote]
Swift Lazy Properties with a Closure
import Foundation class Name {
var name: String? lazy var greet: String = {[weak self] in guard let s = self else { return "Unable to unwrap self" } guard let n = s.name else { return "No name found" }
return "Hi, \(n)" }() init(name: String) { self.name = name }
} var myName = Name(name: "BaluNaik") print(myName.greet) //prints Hi, BaluNaik
Important Points
- We’ve defined a closure inside the lazy var property in the above code.
- The Closure returns a String.
- To eliminate a strong reference cycle, we’ve captured a weak self.
- guard let is used for optional unwrapping.
Let’s do a few modifications to the above code and see how it behaves.
var myName = Name(name: "BaluNaik") print(myName.greet) //prints Hi, BaluNaik myName.name = nil print(myName.greet) //prints Hi, BaluNaik
The above snippet is an interesting case that shows that lazy var property is re-used everytime. Changes to the name property does nothing.
var myName = Name(name: "BaluNaik") myName.name = nil print(myName.greet) //prints No name found
In the above snippet, the second guard let the statement fails to unwrap the optional string.
Swift Computed Properties
Unlike Stored, Computed Properties don’t store values. Instead, they’re used as getters and optional setters to retrieve and set other properties and values indirectly.
A basic example is given below.
import Foundation struct Rect { var length: Double let breadth: Double var area: Double { get { return length*breadth } set(newArea) { length = newArea/breadth } } } var r = Rect(length: 3, breadth: 4) print(r.area) //prints 12.0 r.area = 40 print(r.length) //prints 10.0
In the above code getters and setters are used as get { } and set(param_name){ } on the computed property area. The getters and setters are accessed using the dot syntax.
If a param name isn’t specified inside the setter, Swift assigns the default name as newValue.
import Foundation struct Rect { var length: Double let breadth: Double var area: Double { get { return length*breadth } set { length = newValue/breadth } } }
Computed Properties can’t be assigned as a lazy var property. Computed Properties that have a get and set defined can’t be set as a constant let.
Read-only Computed Properties
A Computed property without a setter is a read-only computed property. They can be defined as a constant.
import Foundation struct Rect { var length: Double let breadth: Double var area: Double { get { return length*breadth } } } var r = Rect(length: 6, breadth: 5) r.area = 50 //Error: Cannot assign to property: 'area' is a get-only property
We can let go of the get keyword in the above case too:
var area: Double { return length*breadth }
Swift Property Observers
Swift Property Observers respond to changes in the property value. These are typically used when two property values are dependent on each other. They contain two methods:
- willSet: This gets triggered just before the value is stored. It allows us to read the old value before its changed. We can access the new value using the keyword newValue
- didSet: This gets triggered after the value is stored. It lets us read both old and new values. We can access the old value using the keyword oldValue
Property Observers get triggered every time the value is set. Let’s use Property Observers in an example where we need to convert yards to inches.
import Foundation struct yardToInchesConversion { var yard: Double = 0 { willSet { print("new value of yards \(newValue)") } didSet { print("old value of yards \(oldValue)") inches = yard*36 print("Updated value of inches \(inches)") } } var inches : Double = 0 } var yi = yardToInchesConversion() yi.yard = 22 //The Following gets printed on the console: new value of yards 22.0 old value of yards 0.0 Updated value of inches 792.0
Swift Global and Local Properties
Global variables are variables that are defined outside of any function, method, closure, or type context. Local variables are variables that are defined within a function, method, or closure context.
Global constants and variables are always computed lazily without the need to be marked with a lazy modifier.
Swift Type Properties
- Type Properties are used on the type(class/struct/enum) instead of the instance of that type.
- Type Properties are defined with the keyword static.
- Static Type Properties can’t be overridden in the subclass. The keyword class is used on the computed properties in this case.
- class keyword isn’t supported with stored properties.
The following code snippets demonstrate the above concepts clearly.
import Foundation class classA { static var i: Int = 5 static var name: String { return "Hello World" } class var multiplyByANumber: Int { return i*5 } //class var j: Int = 1 //Not supported. static func printI() { print("Value of i is \(i)") } class func appendClassName() { print("Class A Hello World") } } class classB: classA { //static var i = 10 Won't Compile override class var multiplyByANumber: Int { return i*5*5 } override class func appendClassName() { print("Class B Hello World") } } classA.appendClassName() //prints Class A Hello World classA.printI() //prints Value of i is 5 classB.appendClassName() //prints Class B Hello World classB.printI() . //prints value of i is 5
Swift Subscripts
Swift Subscripts are shortcuts for accessing the member elements of a collection, list, or sequence. To access an element from an array, dictionary or a list we use the form array[index], dictionary[key], etc. Similarly, we can define a subscript for any type.
We can define multiple subscripts for the same type and the appropriate subscript overload to use is selected based on the type of index value you pass to the subscript.
The syntax for a subscript is similar to computed properties as shown below.
subscript(index: Int) -> Int { get { // return an appropriate subscript value here } set(newValue) { // perform a suitable setting action here } }
An example implementation of subscripts is given below.
import Foundation class testClass { private var month = ["Jan", "Feb", "March", "April"] subscript(index: Int) -> String { get { return month[index] } set(newValue) { self.month[index] = newValue } } } var obj = testClass() print(obj[3]) //prints April