Day 10
Day 10 êŽë š
Classes
At first, classes seem very similar to structs because we use them to create new data types with properties and methods. However, they introduce a new, important, and complex feature called inheritance â the ability to make one class build on the foundations of another.
This is a powerful feature, thereâs no doubt about it, and there is no way to avoid using classes when you start building real iOS apps. But please remember to keep your code simple: just because a feature exists, it doesnât mean you need to use it. As Martin Fowler wrote, âany fool can write code that a computer can understand, but good programmers write code that humans can understand.â
Today you have eight one-minute videos to watch, and youâll meet things like method overrides, final classes, and deinitializers. Once youâve watched each video and optionally gone through the extra reading, thereâs a short test to help make sure youâve understood what was taught.
1. Creating your own classes
1. Creating your own classes
Classes are similar to structs in that they allow you to create new types with properties and methods, but they have five important differences and Iâm going to walk you through each of those differences one at a time.
The first difference between classes and structs is that classes never come with a memberwise initializer. This means if you have properties in your class, you must always create your own initializer.
For example:
class Dog {
var name: String
var breed: String
init(name: String, breed: String) {
self.name = name
self.breed = breed
}
}
Creating instances of that class looks just the same as if it were a struct:
let poppy = Dog(name: "Poppy", breed: "Poodle")
1. Creating your own classes - Additional
2. Class inheritance
2. Class inheritance
The second difference between classes and structs is that you can create a class based on an existing class â it inherits all the properties and methods of the original class, and can add its own on top.
This is called class inheritance or subclassing, the class you inherit from is called the âparentâ or âsuperâ class, and the new class is called the âchildâ class.
Hereâs the Dog
class we just created:
class Dog {
var name: String
var breed: String
init(name: String, breed: String) {
self.name = name
self.breed = breed
}
}
We could create a new class based on that one called Poodle
. It will inherit the same properties and initializer as Dog
by default:
class Poodle: Dog {
}
However, we can also give Poodle
its own initializer. We know it will always have the breed âPoodleâ, so we can make a new initializer that only needs a name
property. Even better, we can make the Poodle
initializer call the Dog
initializer directly so that all the same setup happens:
class Poodle: Dog {
init(name: String) {
super.init(name: name, breed: "Poodle")
}
}
For safety reasons, Swift always makes you call super.init()
from child classes â just in case the parent class does some important work when itâs created.
2. Class inheritance - Additional
3. Overriding methods
3. Overriding methods
Child classes can replace parent methods with their own implementations â a process known as overriding. Hereâs a trivial Dog
class with a makeNoise()
method:
class Dog {
func makeNoise() {
print("Woof!")
}
}
If we create a new Poodle
class that inherits from Dog
, it will inherit the makeNoise()
method. So, this will print âWoof!â:
class Poodle: Dog {
}
let poppy = Poodle()
poppy.makeNoise()
Method overriding allows us to change the implementation of makeNoise()
for the Poodle
class.
Swift requires us to use override func
rather than just func
when overriding a method â it stops you from overriding a method by accident, and youâll get an error if you try to override something that doesnât exist on the parent class:
class Poodle: Dog {
override func makeNoise() {
print("Yip!")
}
}
With that change, poppy.makeNoise()
will print âYip!â rather than âWoof!â.
3. Overriding methods - Additional
- Optional: When would you want to override a method?
- Test: Overriding methods
4. Final classes
4. Final classes
Although class inheritance is very useful â and in fact large parts of Appleâs platforms require you to use it â sometimes you want to disallow other developers from building their own class based on yours.
Swift gives us a final
keyword just for this purpose: when you declare a class as being final, no other class can inherit from it. This means they canât override your methods in order to change your behavior â they need to use your class the way it was written.
To make a class final just put the final
keyword before it, like this:
final class Dog {
var name: String
var breed: String
init(name: String, breed: String) {
self.name = name
self.breed = breed
}
}
4. Final classes - Additional
- Optional: Which classes should be declared as final?
- Test: Final classes
5. Copying objects
5. Copying objects
The third difference between classes and structs is how they are copied. When you copy a struct, both the original and the copy are different things â changing one wonât change the other. When you copy a class, both the original and the copy point to the same thing, so changing one does change the other.
For example, hereâs a simple Singer
class that has a name
property with a default value:
class Singer {
var name = "Taylor Swift"
}
If we create an instance of that class and prints its name, weâll get âTaylor Swiftâ:
var singer = Singer()
print(singer.name)
Now letâs create a second variable from the first one and change its name:
var singerCopy = singer
singerCopy.name = "Justin Bieber"
Because of the way classes work, both singer
and singerCopy
point to the same object in memory, so when we print the singer name again weâll see âJustin Bieberâ:
print(singer.name)
On the other hand, if Singer
were a struct then we would get âTaylor Swiftâ printed a second time:
struct Singer {
var name = "Taylor Swift"
}
5. Copying objects - Additional
- Optional: Why do copies of a class share their data?
- test: Copying objects
6. Deinitializers
6. Deinitializers
The fourth difference between classes and structs is that classes can have deinitializers â code that gets run when an instance of a class is destroyed.
To demonstrate this, hereâs a Person
class with a name
property, a simple initializer, and a printGreeting()
method that prints a message:
class Person {
var name = "John Doe"
init() {
print("\(name) is alive!")
}
func printGreeting() {
print("Hello, I'm \(name)")
}
}
Weâre going to create a few instances of the Person
class inside a loop, because each time the loop goes around a new person will be created then destroyed:
for _ in 1...3 {
let person = Person()
person.printGreeting()
}
And now for the deinitializer. This will be called when the Person
instance is being destroyed:
deinit {
print("\(name) is no more!")
}
6. Deinitializers - Additional
7. Mutability
7. Mutability
The final difference between classes and structs is the way they deal with constants. If you have a constant struct with a variable property, that property canât be changed because the struct itself is constant.
However, if you have a constant class with a variable property, that property can be changed. Because of this, classes donât need the mutating
keyword with methods that change properties; thatâs only needed with structs.
This difference means you can change any variable property on a class even when the class is created as a constant â this is perfectly valid code:
class Singer {
var name = "Taylor Swift"
}
let taylor = Singer()
taylor.name = "Ed Sheeran"
print(taylor.name)
If you want to stop that from happening you need to make the property constant:
class Singer {
let name = "Taylor Swift"
}
7. Mutability - Additional
8. Classes summary
8. Classes summary
Youâve made it to the end of the eighth part of this series, so letâs summarize:
- Classes and structs are similar, in that they can both let you create your own types with properties and methods.
- One class can inherit from another, and it gains all the properties and methods of the parent class. Itâs common to talk about class hierarchies â one class based on another, which itself is based on another.
- You can mark a class with the
final
keyword, which stops other classes from inheriting from it. - Method overriding lets a child class replace a method in its parent class with a new implementation.
- When two variables point at the same class instance, they both point at the same piece of memory â changing one changes the other.
- Classes can have a deinitializer, which is code that gets run when an instance of the class is destroyed.
- Classes donât enforce constants as strongly as structs â if a property is declared as a variable, it can be changed regardless of how the class instance was created.
8. Classes summary - Additional
- Test: Classes