NEW: Learn iOS 11 with my latest book! >>

< Previous: Structs   Next: Properties >

Classes

Swift has another way of building complex data types called classes. They look similar to structs, but have a number of important differences, including:

  • You don't get an automatic memberwise initializer for your classes; you need to write your own.
  • You can define a class as being based off another class, adding any new things you want.
  • When you create an instance of a class it’s called an object. If you copy that object, both copies point at the same data by default – change one, and the copy changes too.

All three of those are massive differences, so I'm going to cover them in more depth before continuing.

Initializing an object

If we were to convert our Person struct into a Person class, Swift wouldn't let us write this:

class Person {
    var clothes: String
    var shoes: String
}

This is because we're declaring the two properties to be String, which if you remember means they absolutely must have a value. This was fine in a struct because Swift automatically produces a memberwise initializer for us that forced us to provide values for the two properties, but this doesn't happen with classes so Swift can't be sure they will be given values.

There are three solutions: make the two values optional strings, give them default values, or write our own initializer. The first option is clumsy because it introduces optionals all over our code where they don't need to be. The second option works, but it's a bit wasteful unless those default values will actually be used. That leaves the third option, and really it's the right one: write our own initializer.

To do this, create a function inside the class called init() that takes the two parameters we care about:

class Person {
    var clothes: String
    var shoes: String

    init(clothes: String, shoes: String) {
        self.clothes = clothes
        self.shoes = shoes
    }
}

There are two things that might jump out at you in that code. First, you don't write func before your init() function, because it's special. Second, because the parameter names being passed in are the same as the names of the properties we want to assign, you use self. to make your meaning clear – "the clothes property of this object should be set to the clothes parameter that was passed in." You can give them unique names if you want – it's down to you.

There are two more things you ought to know but can't see in that code. First, when you write a function inside a class, it's called a method instead. In Swift you write func whether it's a function or a method, but the distinction is preserved when you talk about them.

Second, Swift requires that all non-optional properties have a value by the end of the initializer, or by the time the initializer calls any other method – whichever comes first.

Class inheritance

The second difference between classes and structs are that classes can build on each other to produce greater things, known as class inheritance. This is a technique used extensively in Cocoa Touch, even in the most basic programs, so it's something you should get to grips with.

Let's start with something simple: a Singer class that has properties, which is their name and age. As for methods, there will a simple initializer to handle setting the properties, plus a sing() method that outputs some words:

class Singer {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    func sing() {
        print("La la la la")
    }
}

We can now create an instance of that object by calling that initializer, then read out its properties and call its method:

var taylor = Singer(name: "Taylor", age: 25)
taylor.name
taylor.age
taylor.sing()

That's our basic class, but we're going to build on it: I want to define a CountrySinger class that has everything the Singer class does, but when I call sing() on it I want to print "Trucks, guitars, and liquor" instead.

You could of course just copy and paste the original Singer into a new class called CountrySinger but that's a lazy way to program and it will come back to haunt you if you make later changes to Singer and forget to copy them across. Instead, Swift has a smarter solution: we can define CountrySinger as being based off Singer and it will get all its properties and methods for us to build on:

class CountrySinger: Singer {

}

That colon is what does the magic: it means "CountrySinger extends Singer." Now, that new CountrySinger class (called a subclass) doesn't add anything to Singer (called the parent class, or superclass) yet. We want it to have its own sing() method, but in Swift you need to learn a new keyword: override. This means "I know this method was implemented by my parent class, but I want to change it for this subclass."

Having the override keyword is helpful, because it makes your intent clear. It also allows Swift to check your code: if you don't use override Swift won't let you change a method you got from your superclass, or if you use override and there wasn't anything to override, Swift will point out your error.

So, we need to use override func, like this:

class CountrySinger : Singer {
    override func sing() {
        print("Trucks, guitars, and liquor")
    }
}

Now modify the way the taylor object is created:

var taylor = CountrySinger(name: "Taylor", age: 25)
taylor.sing()

If you change CountrySinger to just Singer you should be able to see the different messages appearing in the results pane.

Now, to make things more complicated, we're going to define a new class called HeavyMetalSinger. But this time we're going to store a new property called noiseLevel defining how loud this particular heavy metal singer likes to scream down their microphone.

This causes a problem, and it's one that needs to be solved in a very particular way:

  • Swift wants all non-optional properties to have a value.
  • Our Singer class doesn't have a noiseLevel property.
  • So, we need to create a custom initializer for HeavyMetalSinger that accepts a noise level.
  • That new initializer also needs to know the name and age of the heavy metal singer, so it can pass it onto the superclass Singer.
  • Passing on data to the superclass is done through a method call, and you can't make method calls in initializers until you have given all your properties values.
  • So, we need to set our own property first (noiseLevel) then pass on the other parameters for the superclass to use.

That might sound awfully complicated, but in code it's straightforward. Here's the HeavyMetalSinger class, complete with its own sing() method:

class HeavyMetalSinger : Singer {
    var noiseLevel: Int

    init(name: String, age: Int, noiseLevel: Int) {
        self.noiseLevel = noiseLevel
        super.init(name: name, age: age)
    }

    override func sing() {
        print("Grrrrr rargh rargh rarrrrgh!")
    }
}

Notice how its initializer takes three parameters, then calls super.init() to pass name and age on to the Singer superclass - but only after its own property has been set. You'll see super used a lot when working with objects, and it just means "call a method on the class I inherited from. It's usually used to mean "let my parent class do everything it needs to do first, then I'll do my extra bits."

Class inheritance is a big topic so don't fret if it's not clear just yet. However, there is one more thing you need to know: class inheritance often spans many levels. For example, A could inherit from B, and B could inherit from C, and C could inherit from D, and so on. This lets you build functionality and re-use up over a number of classes, helping to keep your code modular and easy to understand.

Values vs References

When you copy a struct, the whole thing is duplicated, including all its values. This means that changing one copy of a struct doesn't change the other copies – they are all individual. With classes, each copy of an object points at the same original object, so if you change one they all change. Swift calls structs "value types" because they just point at a value, and classes "reference types" because objects are just shared references to the real value.

This is an important difference, and it means the choice between structs and classes is an important one:

  • If you want to have one shared state that gets passed around and modified in place, you're looking for classes. You can pass them into functions or store them in arrays, modify them in there, and have that change reflected in the rest of your program.
  • If you want to avoid shared state where one copy can't affect all the others, you're looking for structs. You can pass them into functions or store them in arrays, modify them in there, and they won't change wherever else they are referenced.

If I were to summarize this key difference between structs and classes, I'd say this: classes offer more flexibility, whereas structs offer more safety. As a basic rule, you should always use structs until you have a specific reason to use classes.

Hacking with watchOS

Transfer your Swift skills to watchOS the easy way, and learn to build real-world apps in the process!

< Previous: Structs   Next: Properties >
Click here to visit the Hacking with Swift store >>