TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

SOLVED: Cannot override mutable property with read-only property error?

Forums > 100 Days of SwiftUI

Hi! Can anyone help me validate my solution in checkpoint 7? I feel like it's already close to being solved although it's not yet 100% solved.

I'm not sure why I'm also encountering the "cannot override mutable property with read-only property 'isTame' error even though I've stated that I want to override it. Lastly, how do I call these classes? Do I simply write var animal = Animal() then print my var? var corgi = Corgi() print(corgi)? Because I tried it but it doesn't seem to work even though I have passed how many legs they should have inside my class already.

Here's my solution

// HWS Checkpoint 7
// Make a class hierarchy for animals
// Start with an Animal. Add a "legs" property for the number of legs an Animal has.
// Make a Dog subclass of Animal, giving it a speak() method that prints a dog
// barking string. Each subclass should print something different.
// Make Corgi and Poodle subclasses of Dog.
// Make Cat an animal subclass. Add a speak() method with each subclass printing
// something different, and an isTame Boolean, set with an initializer.
// Make Persian and Lion as subclasses of Cat.

class Animal {
    // We should always initialize a value for our properties or it won't work
    // var legs: Int = 0
    var legs: Int

    // If we don't want to initialize the value like what we did above (var legs = 0), we
    // can provide an initializer for our class like so.
    init(legs: Int) {
        self.legs = legs
    }
}

// Here, we're using inheritance and our Dog class inherits all of the properties
// of our parent class (Animal). We can change also modify the values of the inherited properties.
class Dog: Animal {
    // Since we used an initializer in our parent class (Animal), we have to override
    // the whole initializer.
    // If we initialized the value by just using var legs: Int = 0, we can simply override it by
    // using override var legs: Int { return 4 } Overriding anything should always be
    // wrapped in curly braces {}
    override init(legs: Int) {
        // Here, we have to use the super keyword so that it can reference the initializer of our parent class.
        // "super" keyword also allows us to edit the existing initializer.
        // (legs: legs) means we're passing the first parameter legs (in our child class) of whatever
        // value that we set via self.legs into our parent property legs (second parameter :legs)
        super.init(legs: legs)

        // We are now overriding the value of our property from 0 into 4.
        self.legs = 4
    }

    func speak() {
        var bark: String = "arf"
        print(bark)
    }
}

// Our class Corgi would now inherit our Dog class
class Corgi: Dog {
    // Since corgi is also a dog, and dogs have 4 legs, we no longer need to override legs.

    // The way a dog barks differs depending on its breed therefore, we have to override our func speak()
    override func speak() {
        var bark: String = "corgi woof"
        print(bark)
    }
}

// We create another subclass of Dog named Poodle.
class Poodle: Dog {
    override func speak(){
        print("poodle bark")
    }
}

class Cat: Animal {
    // Create a new variable named isTame which is of type bool and set its default value to true.
    var isTame: Bool = true;

    // We also have to override the number of legs for our cat since our parent class (Animal) has the
    // default value set to 0
    override init(legs: Int) {
        super.init(legs: legs)
        self.legs = 4
    }

    // Since Cat and Dog are in the same level, we have to create a new function named speak for our
    // Cat class as well.
    func speak() {
        print("default cat sounds")
    }
}

class Persian: Cat {
    // Persian Cats are tamed so we don't need to edit the default isTame property.

    override func speak() {
        print("meow")
    }
}

class Lion: Cat {
    // Lions aren't tamed so we have to override the isTame property
    override var isTame: Bool { -> ERROR HERE
        return false
    }

    // Lions produce a different sound which is why we have to override our sound func as well.
    override func speak () {
        print("growl")
    }
}

2      

Hi! I think you are a bit overusing override. Here is how one way how you can handle this. Other convenience inits can be created as well.

class Animal {
    var legs: Int

    init(legs: Int) {
        self.legs = legs
    }
}

class Dog: Animal {
    // this is how you can provide parameter by default but you can use different number upon init of class
    init(legs: Int = 4) {
        super.init(legs: legs)
    }

    func speak() {
        var bark: String = "arf"
        print(bark)
    }
}

class Corgi: Dog {
    override func speak() {
        var bark: String = "corgi woof"
        print(bark)
    }
}

class Poodle: Dog {
    override func speak(){
        print("poodle bark")
    }
}

class Cat: Animal {
    var isTame: Bool

    // here you will be asked to provide parameter isTame but you set true by default or you can use your choice upon init of class.
    init(isTame: Bool = true) {
        self.isTame = isTame
        super.init(legs: 4)
    }

    func speak() {
        print("default cat sounds")
    }
}

class Persian: Cat {
    override func speak() {
        print("meow")
    }
}

class Lion: Cat {
    override func speak () {
        print("growl")
    }
}

let persian = Persian() // this will be tame by default as set true as default parameter
let lion = Lion(isTame: false) // here you explicitly provide parameter

persian.speak() // prints meow
lion.speak() // prints growl

if you want Lion class to be false upon init you can do as follows:

class Lion: Cat {

    init() {
        super.init(isTame: false) // here you call Cat init and set isTame to false
    }

    override func speak () {
        print("growl")
    }
}

let persian = Persian() // this will be tame by default as set true as default parameter
let lion = Lion() // as you set isTame to be false on init of the class it will become false

persian.isTame // true
lion.isTame // false

2      

So I really have to put it inside init if I want to override it? I can't just set it like a normal variable and just override the variable afterwards without using initializers? Also, I've searched convenience init up since it's the first time that I'm encountering that term and found that you have to place the keyword convenience before your initializers. However, I didn't see you put any convenience inits. Is there any reason as to why? Lastly, is there a huge difference between normal inits and convenience inits?

2      

Hacking with Swift is sponsored by RevenueCat.

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

All classes need an init. This is called the "designated initializer" and is the default way to initialize the class.

Convenience initializers are additional initializers that provide alternate ways of creating your class. They have to call the designated initializer.

An example:

class RectangularShape {
    var area: Int

    init(_ area: Int) {
        self.area = area
    }

    convenience init(height: Int, width: Int) {
        self.init(height * width)
    }
}

So this allows you to initialize an instance of the RectangularShape class in one of two ways:

let rect1 = RectangularShape(32)

let rect2 = RectangularShape(height: 4, width: 8)

2      

in rect1, does swift also know what our height and width would be? Or it doesn't need to know which is which?

2      

Height and width are not properties of the RectangularShape class. height and width are merely used as parameters to the convenience init to calculate the area, which is the only property that RectangularShape cares about.

2      

Maybe these examples will provide more clarity for using override.

class Animal {
    var legs: Int

    init(legs: Int) {
        self.legs = legs
    }
}

class Dog: Animal {
    let breed: String

    init(breed: String, legs: Int) {
        self.breed = breed
        super.init(legs: legs)
    }

    func speak() {
        print("Dog barks")
    }
}

let mongrel = Dog(breed: "Mongrel", legs: 4)

class Poodle: Dog {

    // Here you will need to use override
    // as you use init with same parameters as parent class i.e. Dog
    override init(breed: String = "Poodle", legs: Int = 4) {
        super.init(breed: breed, legs: legs)
    }

    // the same here as methond name and parameters match parent one
    // you have to use override
    override func speak() {
        print("Poodle barks")
    }

    // here you don't need to use override as func signature is different
    // though you still name it speak but parameter is different from parent
    func speak(sound: String) {
        print("Poodle says: \(sound)")
    }
}

class Cat: Animal {
    let breed: String

    // Here by default you provide 4 legs
    init(breed: String, legs: Int = 4) {
        self.breed = breed
        super.init(legs: legs)
    }

    func speak() {
        print("Default cat sound")
    }
}

let unknownCat = Cat(breed: "Unknown")

class Persian: Cat {
    let gender: String

    // Here you don't need to use override
    // as you use init that is different from parent
    init(gender: String, breed: String = "Persian") {
        self.gender = gender
        super.init(breed: breed)
    }

    override func speak() {
        print("Meow, meow")
    }
}

class Lion: Cat {
    let gender: String
    var age: Int

    // Here again you don't need to use override
    // as you use init that is different from parent
    init(gender: String, age: Int, breed: String = "Lion") {
        self.gender = gender
        self.age = age
        super.init(breed: breed)
    }

    override func speak() {
        print("Grrrrr")
    }
}

let persian = Persian(gender: "female")
persian.gender
persian.legs
persian.breed
persian.speak()

let lion = Lion(gender: "male", age: 3)
lion.age
lion.gender
lion.breed
lion.legs
lion.speak()

2      

I did some additional reading and tried to search for more information and here's my current understanding of the different concepts. Kindly help verify if my understanding is now correct.

Initializers - creating something from scratch (we define the different properties and set the values for them).

Convenience Initializers - we have an existing template and we just update whatever we want to update.

Overriding - process of editing the properties depending on what we want.

Initializers - We set an init and define the different properties that we want.

class Dog: Animal {
    let breed: String -> property breed 

    init(breed: String, legs: Int) { -> **Initializer where we define and edit the property that we want (?)**
        self.breed = breed
        super.init(legs: legs)
    }

    func speak() {
        print("Dog barks")
    }
}

Convenience Initializers

class RectangularShape {
    var area: Int

    init(_ area: Int) {
        self.area = area
    }

    convenience init(height: Int, width: Int) { -> **height and width are already existing and we did not define them. Swift already has these properties (?)**
        self.init(height * width)
    }
}

Overriding We simply add the keyword override and edit the values of the initializers to match whatever our class should have

class Persian: Cat { -> **Persian is a child class and it inherits everything the parent (Cat) class has.**
    let gender: String

    // Here you don't need to use override
    // as you use init that is different from parent
    init(gender: String, breed: String = "Persian") { -> **We did not override because all cats have their own gender regardless of their breed (?); We just have this in order for us to specify the gender and breed of our class Persian when creating an instance of it. Additional question: is breed still a necessary parameter here? Isn't this only for Persian?**
        self.gender = gender
        super.init(breed: breed)
    }

    override func speak() { -> **we added override since not all cats say meow but Persian cats does and meowing is appropriate for this situation.**
        print("Meow, meow")
    }
}

2      

Initializers - creating something from scratch (we define the different properties and set the values for them).

Exactly, in more academic language it means: The chief task of an initializer is to ensure that all properties have an intial value, making the instance well-formed as it comes into existence: and initializer may have other tasks to perform that are essential to the inital state and integrity of the instance. A class, however, may have a superclass, which may have properties and initializers of its own. Thus we must somehow ensure that when a subclass is initialized, its superclass’s properties are initialized and the tasks of its initializers are performed in good order, in addition to those of the subclass itself.

Designated initializer

A class initializer, by default, is a designated initializer. A class can be instantiated only through a call to one of its designated initializers. A designated initializer must see to it that all stored properties are initialized.

Convenience initializer

A convenience initializer is marked with the keyword convenience. A convenience initializer is not how a class is instantiated; it is merely a façade for a designated initializer. A convenience initializer is a delegating initializer; it must contain the phrase self.init(...), which must call a designated initializer in the same class.

Here are some examples. This class has no stored properties, so it has an implicit init() designated initializer:

class Dog { 
} 
let d = Dog() // creates class with no properties

This class’s stored properties have default values, so it has an implicit init() designated initializer too:

class Dog {

var name = "Fido"

} 
let d = Dog() // creates class with name property name "Fido"

This class’s stored properties have default values, but it has no implicit init() initializer because it has an explicit designated initializer:

class Dog {

var name = "Fido"

init(name:String) {
  self.name = name
  }

}

let d = Dog(name:"Rover") // ok

let d2 = Dog() // compile error

This class’s stored properties have default values, and it has an explicit initializer, but it also has an implicit init() initializer because its explicit initializer is a convenience initializer. Moreover, the implicit init() initializer is a designated initializer, so the convenience initializer can delegate to it:

class Dog {

var name = "Fido"

convenience init(name:String) { 
  self.init() // so we call implicit initializer so that you can provide no name just call Dog() and it will assign default value "Fido"
  self.name = name
}

}

let d = Dog(name:"Rover")

let d2 = Dog()

Main thing for convenience init it must call DESIGNATED initializer.

Overriding - process of editing the properties depending on what we want.

Not really so.

Overriding

It is also permitted for a subclass to redefine a method inherited from its superclass. For example, perhaps some dogs bark differently from other dogs. We might have a class NoisyDog, for instance, that is a subclass of Dog. Dog declares bark, but NoisyDog also declares bark, and defines it differently from how Dog defines it. This is called overriding.

You cannot override properties as such using this keyword, you are overriding methods aka functions that have the same name and using the same parameters. init() is also a method where you provide all your properties with initial values.

2      

As for these questions:

init(gender: String, breed: String = "Persian") { -> **We did not override because all cats have their own gender regardless of their breed (?); We just have this in order for us to specify the gender and breed of our class Persian when creating an instance of it. Additional question: is breed still a necessary parameter here? Isn't this only for Persian?**
        self.gender = gender
        super.init(breed: breed)
    }

you use override when you have exactly the same method name which takes the same parameters in your superclass. Here I just used init with different parameters in Persian class than those in Cat class, so they are considered different and you don't need to use override keyword.

Once again to make it maybe more clear. You can use this in Persian class.

let gender: String

init(gender: String) {
        self.gender = gender // here you initialize gender property of Persian class
        super.init(breed: "Persian") // here you can provide default for your Cat class and initialize it, which is a bit different from the one I provided earlier but it does the same task.
        // and now it won't provide you option for initializing with breed as it defaults it to "Persian" but will ask you to provide gender.
    }

There numerous way you can use intializers. All depends on your needs.

for that particular assignment you don't need to go so deep. Just more or less to understand how initialization of classes happens. It comes with time and pratice. When I started, I couldn't undertand that at all :)))) and I have not CS background.

2      

I see. I think I'm getting a bit more understanding about it now. To summarize, initializers allows us to pass in a value as parameter when creating an instance of a class.

let d = Dog(name:"Rover") // ok -> because we have initialized a name for the Dog class which is why we can pass in a custom parameter to edit the name.

let d2 = Dog() // compile error -> because we have to pass an argument since the class has an initializer

The override keyword is only used when we want to edit a function name that has the exact same name with the parent class' function. Otherwise, we don't need to use the override keyword and can create a new function name that doesn't exist in the parent class.

Example: Animal is a parent class with a function legs. Dog is a class that inherits animal but speak() method/function does not exist in the parent class (Animal). Therefore, the keyword "override" is not necessary in our speak method/function.

However, Poodle class will inherit the Dog class and Poodle also needs to use speak(). Therefore, we now need to add an override keyword in the function.

Kindly let me know if my understanding is now correct. Thank you!

2      

Example: Animal is a parent class with a function legs. Dog is a class that inherits animal but speak() method/function does not exist in the parent class (Animal). Therefore, the keyword "override" is not necessary in our speak method/function.

However, Poodle class will inherit the Dog class and Poodle also needs to use speak(). Therefore, we now need to add an override keyword in the function.

This is correct!

let d = Dog(name:"Rover") // ok -> because we have initialized a name for the Dog class which is why we can pass in a custom parameter to edit the name.

let d2 = Dog() // compile error -> because we have to pass an argument since the class has an initializer

class Dog {

var name = "Fido"

// this is explicit initializer as we explicitly ask to use it. It overrides any implicit initializer that is why in the example Dog() it did not work.
init(name:String) {
  self.name = name
  }

}

let d = Dog(name:"Rover") // ok

let d2 = Dog() // compile error

but if you want both to be accessible you change your code like so for convenience:

class Dog {

var name = "Fido"

convenience init(name:String) { 
  self.init() // so we call implicit initializer so that you can provide no name just call Dog() and it will assign default value "Fido"
  self.name = name
}

}

let d = Dog(name:"Rover")

let d2 = Dog() // now you can initialize like so as well.

2      

I see. Thanks a lot man!

2      

Hacking with Swift is sponsored by RevenueCat.

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.