100 Days of SwiftUI Day 12

100 Days of SwiftUI – Day 12 – Classes

Over the past few days, we focused on structs and using access control, static properties and methods. Today, we’re focussing on classes, which are not too dissimilar to structs. Let’s dive in!

Creating a class in SwiftUI

Creating a class in SwiftUI is much like creating a struct. The syntax is almost the same. So what are the key differences? Allow me to quote from Paul’s excellent article:

First, the things that classes and structs have in common include:

  • You get to create and name them.
  • You can add properties and methods, including property observers and access control.
  • You can create custom initializers to configure new instances however you want.

However, classes differ from structs in five key places:

  1. You can make one class build upon functionality in another class, gaining all its properties and methods as a starting point. If you want to selectively override some methods, you can do that too.
  2. Because of that first point, Swift won’t automatically generate a memberwise initializer for classes. This means you either need to write your own initializer, or assign default values to all your properties.
  3. When you copy an instance of a class, both copies share the same data – if you change one copy, the other one also changes.
  4. When the final copy of a class instance is destroyed, Swift can optionally run a special function called a deinitializer.
  5. Even if you make a class constant, you can still change its properties as long as they are variables.

Now that we know the difference, let’s look at an example.

// Create class car

class car {
    let manufacturer: String
    
    init (manufacturer: String) {
        self.manufacturer = manufacturer
    }
}

var tesla = car(manufacturer: "Tesla")
print(tesla.manufacturer)

Inheriting from other classes

Swift allows us to base classes on other classes. This is called inheritance. The class we inherit from is called the super class or parent class. The classes that inherit from the super class are called sub classes or child classes. When inheriting from a super class, both properties and methods of the super class are inherited to the sub class.

In the example below, I’ve added inheritance and an overridden method, as well as initializers for the classes.

class vehicle {
    let manufacturer: String
    let numberOfWheels: Int
    let numberOfDoors: Int
    
    func printSummary() {
        print("This vehicle is manufactured by \(manufacturer). It has \(numberOfDoors) doors and \(numberOfWheels) wheels.")
    }
    
    init(manufacturer: String, numberOfWheels: Int, numberOfDoors: Int) {
        self.manufacturer = manufacturer
        self.numberOfWheels = numberOfWheels
        self.numberOfDoors = numberOfDoors
    }
}

class car: vehicle {
    let model: String
    
    override func printSummary() {
        print("This vehicle is manufactured by \(manufacturer). It has \(numberOfDoors) doors and \(numberOfWheels) wheels. It's a \(model).")
    }
    
    init (manufacturer: String, numberOfWheels: Int, numberOfDoors: Int, model: String) {
        self.model = model
        super.init(manufacturer: manufacturer, numberOfWheels: numberOfWheels, numberOfDoors: numberOfDoors)
    }
}

var vwpolo = car(manufacturer: "Volkswagen", numberOfWheels: 4, numberOfDoors: 5, model: "Polo")
vwpolo.printSummary()

// Prints "This vehicle is manufactured by Volkswagen. It has 5 doors and 4 wheels. It's a Polo."

Copying a class

This part will be quite short! When copying an instance of a class, you essentially copy a reference to the same data as the initial instance. This is different compared to copying a struct, where a copy creates a new unique instance. The example below builds on the code from the example above.

var audi = car(manufacturer: "Audi", numberOfWheels: 4, numberOfDoors: 4, model: "A4")
var secondAudi = audi
audi.model = "New Model"

audi.printSummary()
secondAudi.printSummary()

// Both print "This vehicle is manufactured by Audi. It has 4 doors and 4 wheels. It's a New Model." Because, they both point to the same data.

Using deinitizialers

In this case, Paul’s explanation would be best suited:

Swift’s classes can optionally be given a deinitializer, which is a bit like the opposite of an initializer in that it gets called when the object is destroyed rather than when it’s created.

class vehicle {
    let manufacturer: String
    let numberOfWheels: Int
    let numberOfDoors: Int
    
    func printSummary() {
        print("This vehicle is manufactured by \(manufacturer). It has \(numberOfDoors) doors and \(numberOfWheels) wheels.")
    }
    
    init(manufacturer: String, numberOfWheels: Int, numberOfDoors: Int) {
        self.manufacturer = manufacturer
        self.numberOfWheels = numberOfWheels
        self.numberOfDoors = numberOfDoors
    }
}

class car: vehicle {
    var model: String
    
    override func printSummary() {
        print("This vehicle is manufactured by \(manufacturer). It has \(numberOfDoors) doors and \(numberOfWheels) wheels. It's a \(model).")
    }
    
    init (manufacturer: String, numberOfWheels: Int, numberOfDoors: Int, model: String) {
        self.model = model
        super.init(manufacturer: manufacturer, numberOfWheels: numberOfWheels, numberOfDoors: numberOfDoors)
    }
    
    deinit {
        print("This car is now retired!")
    }
}

var vwpolo = car(manufacturer: "Volkswagen", numberOfWheels: 4, numberOfDoors: 5, model: "Polo")
vwpolo.printSummary()

// Prints "This vehicle is manufactured by Volkswagen. It has 5 doors and 4 wheels. It's a Polo."

var audi = car(manufacturer: "Audi", numberOfWheels: 4, numberOfDoors: 4, model: "A4")
var secondAudi = audi
audi.model = "New Model"

audi.printSummary()
secondAudi.printSummary()

// Both print "This vehicle is manufactured by Audi. It has 4 doors and 4 wheels. It's a New Model."

for i in 1...3 {
    let newCar = car(manufacturer: "Manufacturer \(i)", numberOfWheels: 4, numberOfDoors: 4, model: "Model \(i)")
    newCar.printSummary()
}

// * This vehicle is manufactured by Manufacturer 1. It has 4 doors and 4 wheels. It's a Model 1.
This car is now retired!
This vehicle is manufactured by Manufacturer 2. It has 4 doors and 4 wheels. It's a Model 2.
This car is now retired!
This vehicle is manufactured by Manufacturer 3. It has 4 doors and 4 wheels. It's a Model 3.
This car is now retired! * //

SwiftUI checkpoint 7

We’ve covered another major part of SwiftUI, so it’s only fitting to see if we understand all the material with another checkpoint. The challenge:

Make a class hierarchy for animals, starting with Animal at the top, then Dog and Cat as subclasses, then Corgi and Poodle as subclasses of Dog, and Persian and Lion as subclasses of Cat.

But there’s more:

  1. The Animal class should have a legs integer property that tracks how many legs the animal has.
  2. The Dog class should have a speak() method that prints a generic dog barking string, but each of the subclasses should print something slightly different.
  3. The Cat class should have a matching speak() method, again with each subclass printing something different.
  4. The Cat class should have an isTame Boolean property, provided using an initializer.
class Animal {
    let legs: Int
    
    init(legs: Int) {
        self.legs = legs
    }
}

class Dog: Animal {
    func speak() {
        print("\(Dog.self) is barking!")
    }
}

class Corgi: Dog {
    override func speak() {
        print("\(Corgi.self) is barking!")
    }
}

class Poodle: Dog {
    override func speak() {
        print("\(Corgi.self) is barking!")
    }
}

class Cat: Animal {
    let isTame: Bool
    
    func speak() {
        print("Meow, I'm a super class cat!")
    }
    
    init(legs: Int, isTame: Bool) {
        self.isTame = isTame
        super.init(legs: legs)
    }
    
}

class Persian: Cat {
    override func speak() {
        print("Meow, I'm a Persian!")
    }
}

class Lion: Cat {
    override func speak() {
        print("RAWR! I'm a lion!")
    }
}

var corgi = Corgi(legs: 4)
corgi.speak()

var meowth = Persian(legs: 4, isTame: true)
meowth.speak()
print(meowth.isTame)

var simba = Lion(legs: 4, isTame: false)
simba.speak()
print(simba.isTame)

I think my solution covers all the tasks in the challenge, but feel free to let me know if I’ve could’ve done something better or differently!

That was it for day 12. Tomorrow, we’ll tackle protocols, extensions, and checkpoint 8. We march on!

Darryl

Hi! My name is Darryl and this is my personal blog where I write about my journey as I learn programming! You'll also find articles about other things that interest me including games, tech and anime.

Post navigation

Leave a Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

100 Days of SwiftUI – Day 11 – Structs, Part 2

100 Days of SwiftUI – Day 44

100 Days of SwiftUI – Day 97

100 Days of SwiftUI – Day 91