Swift Value and Reference Types, How to Use

Swift Value and Reference Types, How to Use

Based on the memory handling methods, types in Swift are divided into two different categories: value types and reference types. There are some basic differences between value and reference types which causes them to behave differently in the same conditions. So developers should know their specifications to write logical codes both during working with Apple’s predefined types and during defining their own custom types.

To begin with, let’s have an overview of the characteristics of value and reference types.

Value types

  1. Copy by value: every time we assign one variable to another, or we pass a value type to a function, a copy is created instead.
  2. Mutability: If a value type is declared as a constant, none of its members can be mutated, as long as its members are value types themselves.
  3. Performance: value types are created in the stack memory, so they are a lot faster to create and manage in the memory.
  4. Inheritance: structs do not support inheritance.

Reference types

  1. Copy by reference: classes are copied by reference.
  2. Mutability: classes are not as strict about mutability as structs are.
  3. Inheritance: classes support inheritance.

The table below shows the reference and value types in Swift.

Reference TypesValue Types
ClassArray
FunctionDictionary
ClosureSet
Struct (all numbers like Int and
Double, Character,
and String are of the type of Struct)
Enum
Tuple

Copy Method

The most basic distinguishing feature of value types is that copying value type instances or passing them into functions, create an independent instance of them while copying or passing reference types create a shared instance.

struct Person {
    
    var name: String
}

var amir = Person(name: "Amir")
print("Amir name", amir.name)

var john = amir
john.name = "John"
print("John name:", john.name)
print("Amir name:", amir.name)

//Output
//Amir name Amir
//John name: John
//Amir name: Amir

It is clear that copying the Amir into John has created a new instance of Person. Now we are going to do the same for type Car which is same as Person but is of type Class instead of Struct.

class Car {
    
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

let mercedes = Car(name: "Mercedes")
print("Mercedes name", mercedes.name)

var lexus = mercedes
lexus.name = "Lexus"
print("Lexus name:", lexus.name)
print("Mercedes name:", mercedes.name)

//Output
//Mercedes name Mercedes
//Lexus name: Lexus
//Mercedes name: Lexus

Obviously, by changing the lexus name, the mercedes name changed too that shows during the copying of the mercedes, only a reference of it has passed to the lexus, so both lexus and mercedes are referring to a shared instance.

So developers should be aware of this different behavior. One of the mistakes that programmers make normally at the start of programming with Swift is during working with arrays. They normally say I’m changing an array item but id doesn’t change. Look at the following code:

struct Person: CustomStringConvertible {
    var description: String {
        get {
            return self.name
        }
    }
  
    var name: String
}

var people = [Person(name: "John"),
                Person(name: "Perter"),
                Person(name: "Alice")]

print(people)
var alice = people[2]
alice.name = "Jafar"
print(people)

//Output
//[John, Perter, Alice]
//[John, Perter, Alice]

In the above code, I added the “CustomStringConvertible” to use the types easily with the print() method. As you see, even though I changed Alice but it didn’t change in the array. It is clear that copying the people[2] into the alice variable has created a new copy of the alice and this new one name was changed in the next line. So the array has not changed at all. Therefore to change the alice name we need to do it in the array like below.

print(people)
people[2].name = "Jafar"
print(people)

//Output
//[John, Perter, Alice]
//[John, Perter, Jafar]

Additionally, the following code works perfectly with an array of Cars. Now you know the reason. Because Car is a reference type.

class Car: CustomStringConvertible {
    var description: String {
        get {
            return self.name
        }
    }
    
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

var cars = [Car(name: "Mercedes"),
            Car(name: "Lexus"),
            Car(name: "BMW")]

print(cars)
var car = cars[2]
car.name = "Audi"
print(cars)

//Output
//[Mercedes, Lexus, BMW]
//[Mercedes, Lexus, Audi]

Mutability

After the difference in copy method, the most noticeable point in reference and value types is their mutability. Let’s test them with code.

let mike = Person(name: "Mike")
mike.name = "Mice"

In this case, we will encounter the compiler error: “Cannot assign to property: ‘mike’ is a ‘let’ constant”. Since we have defined mike as a constant, by the “let” keyword, the compiler does not allow us to mutate the instance. It sounds good because it makes our code more reliable predictable. What will happen if we do the same with a class like Car?

let mazda = Car(name: "Mazda")
mazda.name = "Nissan"           //Compiles without error
mazda = Car(name: "Toyota")     //Error: Cannot assign to value: 'mazda' is a 'let' contant

As you see, even though we have defined mazda as constant we can change the name of the mazda without error. But in the third line, we cannot assign a new car instance, toyota, to mazda. In fact, defining reference types as constant makes only the variable itself immutable, not its properties, which would not be very handy in many cases.

Defining Our Types

Must of the Swift predefined types, like enum and dictionary, have their own unique characteristics, so during development, deciding about using which of them is not difficult, but Class and Struct have similar functions with some important behavioral differences which makes selecting between them a little tricky.

For the struct, everything seems good, until a reference type as a property is added to it. Suppose each person can have a car.

struct Person: CustomStringConvertible {
    var description: String {
        get {
            return "Name: \(self.name), Car: \(self.car.name)"
        }
    }
    
    var name: String
    var car: Car
}

let bob = Person(name: "Bob", car: Car(name: "Nissan"))
print(bob)
bob.car.name = "Volkswagen"
print(bob)

//Output
//Name: Bob, Car: Nissan
//Name: Bob, Car: Volkswagen

Though the bob was defined as constant, but the name of his car changed easily. Consequently, we lost the immutability feature of struct. Exactly the same way, if copy bob to another variable, mike, we will see that the bob’s car will be shared between bob and mike.

let bob = Person(name: "Bob", car: Car(name: "Nissan"))
print(bob)
let mike = bob
mike.car.name = "Kia"
print(bob)

//Output
//Name: Bob, Car: Nissan
//Name: Bob, Car: Kia

Hence, if we use reference type properties in a struct, we easily will lose the copy by value and immutability features. Therefore, as a rule of thumb:

Use structs as long as all of its properties are value types.

Actually, one of the main use cases of structs could be data models. It means objects that only keep primitive data types and transfer them between the other objects. It is recommended to create data models by structs at first, then if we cannot accomplish our goals using structs, we can change them to class easily.

Conclusion

While structs seem very appealing at first glance, using them in the wrong way can lead us to severe bugs which finding them would be very difficult especially because we have some assumptions about structs that are not happening at the execution of our code. So to play safe, avoid using reference types inside structs. In addition, we should use classes whenever we need inheritance, definitely.


Posted

in

by

Comments

3 responses to “Swift Value and Reference Types, How to Use”

  1. Yusmle Avatar

    Great article! As a Java man, I’m in love with Swift. 🙂
    But, as a lack of your description, you didn’t mention that Swift is pass by value or pass by type. Everything in Swift is passed by “copy” by default, so when you pass a value-type you get a copy of the value, and when you pass a reference type you get a copy of the reference.
    So yes, Swift is pass by value like Java.

    1. amirreza Avatar

      Hey Yousef, thanks for your comment. You’re right.

    2. amirreza Avatar

      I forgot to say that if you pass a value type by inout keyword, it will pass that value type by ref. So you can edit that value inside the function body.