Day 10 - 100 Days of Swift

5 minute read

Classes

Day 10 is about classes. You look at creating classes and inheriting from a parent class. You look at overriding the parent class’s methods and marking your class as final so it can’t be subclassed. You look at the difference between copying a class and copying a struct, and how they handle mutability differently. And you look at how to run code when your class is deinitialized.

You can create your own class in pretty much the same way as you make a struct. The main difference is that you never get a generated initializer for classes, so you either have to give everything a default value or write your own:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Building {
    var street: String
    var buildingNumber: Int

    // Have to provide an initializer
    init (street: String, buildingNumber: Int) {
        self.street = street
        self.buildingNumber = buildingNumber
    }

    func streetAddress() -> String {
        return "\(buildingNumber) \(street)"
    }
}

One big advantage that classes have over structs is that classes can inherit from other classes. This means they get all the properties and methods of the parent class without you having to write them out again:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Office: Building {
    var employees: [Employee]

    init (street: String,
          buildingNumber: Int,
          employees: [Employee] = []) {
        self.employees = employees
        super.init(street: street,
                   buildingNumber: buildingNumber)
    }
}

let theOffice = Office(street: "Slough Ave.",
                       buildingNumber: 1725)

// Offices get all of Buildings properties for free.  
print(theOffice.streetAddress)
// "1725 Slough Ave."

If you want to inherit the methods and properties from a class, but need to customize some of the methods, you can mark those methods with override and provide a new implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Office: Building {
    ... // Everything else is the same

    override func streetAddress() -> String {
        return super.streetAddress() +
        " - \(employees.count) employees"
    }
}

let theOffice = Office(street: "Slough Ave.",
                       buildingNumber: 1725)

// The overridden version will now be used.
print(theOffice.streetAddress())
// "1725 Slough Ave. - 0 employees"

If you write a class and you don’t want it to be subclassed, you can mark is as final to prevent just that:

1
2
3
4
final class Office: Building { ... }

// This isn't allowed. Won't compile.
class SmallOffice: Office { }

If you want to run some code when your class is destroyed, you can do that in the deinit method. Structs don’t get these:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Office: Building {
    ... // Everything else is the same

    deinit {
        print("Sadly we are closing this office.")
    }
}

// Put it in a loop, to demonstrate it being released
for _ in 0..<1 {
    let theOffice = Office(street: "Slough Ave.",
                           buildingNumber: 1725)
    print(theOffice.streetAddress())
}
// "1725 Slough Ave. - 0 employees"
// "Sadly, we are closing this office."

One difference between structs and classes is how they copy. Structs copy by value, classes copy by reference. This means that when you copy a struct you get a copy of its value, but when you copy a class you get a second reference, or pointer, to the same object in memory. This can lead to some confusion if you don’t know what’s going on:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct Employee {
    var name: String
}

var employee1 = Employee(name: "Jim")

print(employee1.name) // "Jim"

var employee2 = employee1
employee2.name = "Dwight"

print(employee1.name) // "Jim"
print(employee2.name) // "Dwight"

class Manager {
    var name: String = "Unknown"
}

var manager1 = Manager()
manager1.name = "Michael"

print(manager1.name) // "Michael"

var manager2 = manager1
manager2.name = "Charles"

print(manager1.name) // "Charles"
print(manager2.name) // "Charles"

There is a similar difference when it comes to mutability. A constant struct cannot change any of its properties, because that will change part of its value, but a constant reference to a class remains the same, even if it’s underlying properties change:

1
2
3
4
5
6
7
let employee3 = Employee(name: "Pam")
employee3.name = "Erin"
// Doesn't work. Won't compile

let manager3 = Manager()
manager3.name = "Jim"
// This works just fine though

Reflections

I always appreciate a good reminder on the distinctions between structs and classes. It is one of those things that I have sort of developed a second sense about over time, so that I can usually figure out what it happening without having to actively remember why one works one way and the other works another. But it is good to bring some of that to the forefront of my mind every now and then.

One thing I learned today was that structs don’t have deinitializers. I don’t know why yet, because they have initializers, so why wouldn’t they have a deinitializer? But it is good to know nonetheless. Now that I know, I’ll be on the lookout for some explanation as to why.