Value versus class types

There are some very fundamental differences between value types (structures, enumerations, and tuples) and reference types (classes). The primary difference is how the instances of value and reference types are passed. When we pass an instance of a value type, we are actually passing a copy of the original instances. This means that the changes made to one instance are not reflected back to the others. When we pass an instance of a reference type, we are passing a reference to the original instance. This means that both references point to the same instance; therefore, a change made to one reference will reflect in the others.

The explanation in the previous paragraph is a pretty straightforward explanation. We have seen this explanation a couple of times already in this book, but it is a very important concept that you must understand. In this section, we are going to examine the difference between value and reference types, so we know the advantages of each and also the pitfalls to avoid when using them.

Let's begin by creating two types. One is going to be a structure (value type) and the other is going to be a class (reference type). We will be using these types in this section to demonstrate the differences between value and reference types. The first type that we will see will be named MyValueType. We will implement MyValueType using a structure, which means that it is a value type as its name tells us:

struct MyValueType {
    var name: String
    var assignment: String
    var grade: Int
}

Within MyValueType, we defined three properties. Two of the properties are of the String type (name and assignment) and one is of the Int type (grade). Now, let's look at how we would implement this as a class:

class MyReferenceType {
    var name: String
    var assignment: String
    var grade: Int
    
    init(name: String, assignment: String, grade: Int) {
        self.name = name
        self.assignment = assignment
        self.grade = grade
    }
}

MyReferenceType defines the same three properties as MyValueType; however, we need to define an initializer in the MyReferenceType type that we did not need to define in the MyValueType type. The reason for this is structures provide us with a default initializer that will initialize all of the properties if we do not provide a default initializer for that type.

Let's look at how we could use each of these types. The following code shows how we could create instances of each of these types:

var ref = MyReferenceType(name: "Jon", assignment: "Math Test 1", grade: 90)
var val = MyValueType(name: "Jon", assignment: "Math Test 1", grade: 90)

As we see in this code, instances of structures are created exactly like the instances of classes. Being able to use the same format to create instances of structures and classes is good because it makes our lives easier. However, we need to keep in mind that value types behave in a different manner than reference types. Let's take a look at this. The first thing we need to do is create two functions that will change the grades for the instances of our two types:

func extraCreditReferenceType(ref: MyReferenceType, extraCredit: Int) {
    ref.grade += extraCredit
}

func extraCreditValueType(var val: MyValueType, extraCredit: Int) {
    val.grade += extraCredit
}

Each of these functions takes an instance of one of our types and also an extra credit amount. Within the function, we will add the extra credit amount to the grade. Now, let's see what happens when we use each of these functions. Let's start off by seeing what happens when we use the MyReferenceType type with the extraCreditReferenceType() function:

var ref = 
    MyReferenceType(name: "Jon", assignment: "Math Test 1", grade: 90)
extraCreditReferenceType(ref, extraCredit:5)
print("Reference: \(ref.name) - \(ref.grade)")

In this code, we created an instance of MyReferenceType with a grade of 90. We then used the extraCreditReferenceType() function to add five extra points to the grade. If we run this code, the following line would be printed in the console:

Reference: Jon - 95

As we can see, five extra credit points were added to the grade. Now let's try to do the same thing with the MyValueType type and the extraCreditValueType() function. The following code shows how to do this:

var val =
    MyValueType(name: "Jon", assignment: "Math Test 1", grade: 90)
extraCreditValueType(val, extraCredit:5)
print("Value: \(val.name) - \(val.grade)")

In this code, we created an instance of MyValueType with a grade of 90. We then used the extraCreditValueType() function to add five extra points to the grade. If we run this code, the following line would be printed in the console:

Value: Jon - 90

As we can see, the five extra credit points are missing from our grade in this example. The reason for this is when we pass an instance of a value type to a function, we are actually passing a copy of the original instance. This means, when we add the extra credit to the grade within the extraCreditValueType() function, we are adding it to a copy of the original instance which means that the change is not reflected back to the original.

Using a value type does protect us from making accidental changes to our instances because the instances are scoped to the function or type that they are created in. Value types also protect us from having multiple references to the same instance. Let's take a look at this, so we can understand the type of issues we may face when we use reference types. We will begin by creating a function that is designed to retrieve the grade for an assignment from a data store. However, to simplify our example, we will simply generate a random score. The following code shows how we would write this function:

func getGradeForAssignment(assignment: MyReferenceType) {
    // Code to get grade from DB
    // Random code here to illustrate issue
    let num = Int(arc4random_uniform(20) + 80)
    assignment.grade = num
    print("Grade for \(assignment.name) is \(num)")
}

This function is designed to retrieve the grade for name and assignment that is defined in the MyReferenceType instance that is passed into the function. Once the grade is retrieved, we will use it to set the grade property of the MyReferenceType instance. We will also print the grade out to the console, so we can see what the grade is. Now, let's see how we would not want to use this function:

var mathGrades = [MyReferenceType]()
var students = ["Jon", "Kim", "Kailey", "Kara"]
var mathAssignment = MyReferenceType(name: "", assignment: "Math Assignment", grade: 0)

for student in students {
    mathAssignment.name = student
    getGradeForAssignment(mathAssignment)
    mathGrades.append(mathAssignment)
}

In the previous code, we created a mathGrades array that will store the grades for our assignment and a students array that will contain the name of the students we wish to retrieve the grades for. We then created an instance of the MyReferenceType class that contains the name of our assignment. We will use this instance to request the grades from the getGradeForAssignment() function. Now that everything is defined, we will loop through the list of students to retrieve the grades. The following is a sample output from this code:

Grade for Jon is 90
Grade for Kim is 84
Grade for Kailey is 99
Grade for Kara is 89

This appears to look exactly like what we want. However, there is a huge bug in this code. Let's loop through our mathGrades array to see what grades we have in the array itself:

for assignment in mathGrades {
    print("\(assignment.name): grade \(assignment.grade)")
}

The output of this code would look as follows:

Kara: grade 89
Kara: grade 89
Kara: grade 89
Kara: grade 89

That is not what we wanted. The reason we see these results is because we created one instance of MyReferenceType and then we kept updating that single instance. This means that we kept overwriting the previous name and grade. Since MyReferenceType is a reference type, all the references in the mathGrades array pointed to the same instance of MyReferenceType that ended up being Kara's grade.

Most veteran object-oriented developers have learned the hard way to be careful to avoid this type of issue, but they do still occasionally happen, especially with junior developers. Using value types can help us avoid these issues; however, there are times when we would like to have this type of behavior. Apple has provided a way for us to do this with the inout parameters. An inout parameter allows us to change the value of a parameter and to have the change persist after the function call has ended.

We define an inout parameter by placing the inout keyword at the start of the parameter's definition. An inout parameter has a value that is passed into the function. This value is then modified by the function and is passed back out of the function to replace the original value.

Let's look at how we can use value types with the inout keyword to create a version of the previous example that will work correctly. The first thing we need to do is modify the getGradesForAssignment() function to use an instance of MyValueType that it can modify:

func getGradeForAssignment(inout assignment: MyValueType) {
    // Code to get grade from DB
    // Random code here to illustrate issue
    let num = Int(arc4random_uniform(20) + 80)
    assignment.grade = num
    print("Grade for \(assignment.name) is \(num)")
}

The only change we made to this function was the way we defined the parameter that was passed in. The property is now defined to be of MyValueType and we added the inout keyword to allow the function to modify the instance that was passed in. Now let's see how we could use this function:

var mathGrades = [MyValueType]()
var students = ["Jon", "Kim", "Kailey", "Kara"]
var mathAssignment = MyValueType(name: "", assignment: "Math Assignment", grade: 0)

for student in students {
    mathAssignment.name = student
    getGradeForAssignment(&mathAssignment)
    mathGrades.append(mathAssignment)
}

for assignment in mathGrades {
   print("\(assignment.name): grade \(assignment.grade)")
}

Once again, this code looks a lot like the code from the previous example. However, we did make two changes. The first is that the mathAssignment variable is now defined to be of the MyValueType type and, when we called the getGradeForAssignment() function, we prefixed the argument with an & ampersand. The ampersand tells us that we are passing a reference to the value type, so any changes made in the function are reflected back to the original instance.

The output of this new code will look as follows:

Grade for Jon is 97
Grade for Kim is 83
Grade for Kailey is 87
Grade for Kara is 85

Jon: grade 97
Kim: grade 83
Kailey: grade 87
Kara: grade 85

The output from this code is what we expected to see, where each instance in the mathGrades array represents a different grade. The reason this code works correctly is that when we add the mathAssignment instance to the mathGrades array, we are adding a copy of the mathAssignment instance to the array. However, when we pass the mathAssignment instance to the getGradeForAssignment() function, we are passing a reference.

There are some things we cannot do with value types that we can do with reference (class) types. The first thing that we will look at is the recursive data types.