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.