Day 10 - 100 Days of Swift
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.