Chapter 5.  Improving Your Code with Value Types

Now that you have a deeper understanding about layout and user interfaces, it's time to take a step back and take a look at the underlying code we're writing to build apps. If you have experience with Object Oriented Programming (OOPs), the way we were programming so far should look familiar to you. You might not have seen protocols used in class declarations before, but apart from that, inheritance in Swift is pretty much the same as you might have seen before.

However, as you have been playing around with Swift and iOS development, you might have noticed two object types you have not seen before: structs and enums. These two object types are what we refer to as value types. What this means and how and why it matters is covered in this chapter.

You will learn what structs and enums are and how you can use them in your own applications. You will also learn how these object types compare to traditional classes and how to make a choice between using a class: enum or a struct. First, we'll look into classes and how they manifest themselves throughout apps a bit more, so you have a better understanding of the situation as you've seen it being set up until now. The topics covered in this chapter are the following:

  • Understanding classes, also known as reference types
  • Value types and how they compare to reference types
  • Improving your code with structs
  • Containing information in enums
  • How to choose between the types

At the end of this chapter, you'll be able to distinguish between different object types and you should be able to use them in your own apps. During this chapter, we'll use a Playground to experiment with code. You can check out the Playground for this chapter in the Git repository for this book or you can create a new Playground and follow along.

Understanding reference types

In the previous chapters, you've been creating classes to contain your app's logic and user interface. This works perfectly fine, and to make use of these classes, we didn't need to know anything about how classes behave on the inside and how they manifest themselves in terms of memory and mutability. To understand value types and how they compare to reference types, it's essential that you do have an understanding of what's going on under the hood.

In Swift, there are two types of object that are considered a reference type: classes and closures. Classes are the only objects in Swift that can inherit from other classes. More on this later when we discuss structs. Let's examine what it means for an object to be a reference type first.

Whenever you create an instance of a class, you have an object that you can use and pass around. For example, you could use an instance of a class as an argument of a method. This is demonstrated in the following snippet:

class Pet { 
    var name: String 
     
    init(name: String) { 
        self.name = name 
    } 
} 
 
func printName(forPet pet: Pet) { 
    print(pet.name) 
} 
 
let cat = Pet(name: "Bubbles") 
printName(forPet: cat) // Bubbles  

The preceding code isn't too exciting. We pass an instance of the Pet class to the printName function and it prints our pet's name. By passing the instance of Pet to a function, we actually passed a reference to our Pet instance to the function. This might sound confusing, and before everything becomes clear, let's make things a little bit more confusing. If you change the printName function as displayed in the following snippet, we can see some behavior that probably confuses you just a bit more:

func printName(forPet pet: Pet) { 
    print(pet.name) 
    pet.name = "Jeff" 
} 
 
let cat = Pet(name: "Bubbles") 
printName(forPet: cat) // Bubbles 
print(cat.name) // Jeff 

After calling the printName function, our cat has a different name. You probably consider this strange; how does setting the name of the pet you received in the function change the name of the cat variable? If you've programmed in other languages, you might consider this obvious; after all, didn't we just pass that exact constant to the printName function? Also, doesn't it make sense then that the name of the original constant also changed? If this is what you thought, then your mindset is in the world of reference types. If this isn't what you thought, you expected our constant to behave more like a value type, which isn't a bad thing at all. However, you still don't really know what exactly is happening with our Pet instance.

The snippet we used to change the Pet's name is small, but it shows exactly how reference types work. Whenever you create an instance of a class, some space in memory is allocated for the instance. When you pass the instance as an argument to a function, you don't pass the contents of the instance. Instead, you pass the memory address or, in other words, a reference to the instance. Unless you explicitly copy a reference type, every time you pass your instance around or point another variable to it, the same address in memory is changed. Let's expand our initial example a bit more, so this becomes even more apparent:

let cat = Pet(name: "Bubbles") 
let dog = cat 
 
printName(forPet: cat) // Bubbles 
dog.name = "Benno" 
 
print(cat.name) // Benno 

Here, we created a cat constant, then we created a dog constant and set the cat constant as its value. Then, we called the printName function; this works as before. Next, we changed the name of the dog. When we print the name of the cat instance, it's not what you might have expected at first because the name of the dog is printed. That's because we didn't copy anything, we simply pointed the dog constant to the same address in memory as the cat pointed to.

Reference types are passed around by the address in memory they point to. Unless you explicitly copy an object, multiple variables or constants are pointing to the same instance of the object. As a result of this, mutating one of these variables will mutate them all.

Although this behavior can be convenient at certain times, it could also turn out to pack some nasty surprises if your apps grow to a substantial size. Therefore, it's a good idea to understand value types and know when to use them because value types can save you quite some debugging time.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset