Swift Properties

Swift Properties are broadly classified into two types.

  1. Stored Properties: store constants and variables and are provided by classes and structures
  2. 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]

Reference: Swift.org

 

Unlike other properties, a lazy property is initialized right before it’s being accessed for the first time.

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

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:

  1. 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
  2. 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

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