In this tutorial, we’ll be discussing about Swift Properties. Properties are an important part of Classes, Structures and Enumerations in Swift.
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 calculate values. Are provided by classes structs as well as enumerations.
Note: A computed property can’t be a constant.
Swift Stored Property Example
An example of Stored Properties is given below.
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 r1 = Rect(length: 5, breadth: 5)
r1.length = 6 //compile-time error.
If the struct is intialised 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:
Unlike other properties, a lazy property is initialised 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 its 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 initialisation completes which is not the case with lazy properties.
- A lazy property must have an initialiser and cannot be used simply with any variable.
The following is a wrong usage case of lazy var
.
struct Rect{
lazy var length : Int //compile-time error. lazy properties require an initaliser.
let breadth : Int
}
An example of lazy var is given below:
truct 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 initialised 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 initialised, for further accesses it generally reuses the first instance.
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.
Swift Lazy Properties with a Closure
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 n = Name(name: "Anupam")
print(n.greet) //prints "Hi, Anupamn"
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 to for optional unwrapping.
Let’s do a few modifications of the above code and see how it behaves.
var n = Name(name: "Anupam")
print(n.greet) //prints "Hi, Anupam"
n.name = nil
print(n.greet) //prints "Hi, Anupam"
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 n = Name(name: "Anupam")
n.name = nil
print(n.greet) //prints "No name found"
In the above snippet, the second guard let 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.
struct Rect{
var length : Double
let breadth : Double
var area : Double {
get{
return length*breadth
}
set(newArea)
{
length = newArea/breadth
}
}
}
var r = Rect(length: 6, breadth: 5)
print(r.area) //prints 30.0
r.area = 40
print(r.length) //prints 8.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
.
struct Rect{
var length : Double
let breadth : Double
var area : Double {
get{
return length*breadth
}
set
{
length = newValue/breadth
}
}
}
Computed Properties can’t be assign 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.
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 //compile-time error. 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 the 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.
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.
class A {
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. Won't compile.
static func printI()
{
print("Value of i is (i)")
}
class func appendClassName()
{
print("Class A Hello World")
}
}
class SubClass : A {
//static var i = 10 Won't Compile
override class var multiplyByANumber : Int{
return i*5*5
}
override class func appendClassName(){
print("Class SubClass Hello World")
}
}
Output of above program:
Class A Hello World
25
Class SubClass Hello World
125
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.
class M {
private var month = ["Jan", "Feb", "March", "April"]
subscript(index: Int) -> String {
get {
return month[index]
}
set(newValue) {
self.month[index] = newValue
}
}
}
var m = M()
m[3] //April
m[0] = "Dec"
This brings an end to Swift properties tutorial.
Reference: Apple Docs