Silver & Gold Challenge - Ch 17

#1

Initialization always throws me for a loop in every language, and Swift is no different. Whoever wants to pick this apart, please feel free.

Silver Challenge

This necessitated the need to remove the required attribute from the Monster and Zombie initializers, and modify Zombie's initializer to be a convenient one that calls its designated initializer, which in turn calls its superclass’s (Monster) initializer. For some reason the compiler was throwing an error for this new initializer, and I’m a little confused–it said I was overriding the superclass’s initializer, but aren’t we doing that in the other convenience initializer? Is it because initialization call is the same, thats why you need to specify that you’re overriding? Any help answering this would clear up a lot of issues in my head.

/// Monster.swift
init(town: Town?, monsterName: String) {
    self.town = town
    name = monsterName
}

I specified limp and fallingApart in the initialization call because I was getting errors trying to set them before initialization.

/// Zombie.swift
convenience override init(town: Town?, monsterName: String) {
    self.init(limp: false, fallingApart: false, town: town, monsterName: monsterName)
}

Gold Challenge

To fix the issue we are experiencing, you have to use a guard statement and change the initializer to denote that it is failable (adding a ?):

init?(town: Town?, monsterName: String) {
    guard monsterName.characters.count > 0 else {
        return nil
    }
    self.town = town
    name = monsterName
}

Now we need to clean up all of Zombie's initializers (just adding ?s everywhere we can):

/// Zombie.swift
init?(limp: Bool, fallingApart: Bool, town: Town?, monsterName: String) {
    walksWithLimp = limp
    isFallingApart = fallingApart
    super.init(town: town, monsterName: monsterName)
}

convenience init?(limp: Bool, fallingApart: Bool) {
    self.init(limp: limp, fallingApart: fallingApart, town: nil, monsterName: "Fred")
    if walksWithLimp {
        print("This zombie has a bad knee.")
    }
}

convenience override init?(town: Town?, monsterName: String) {
    self.init(limp: false, fallingApart: false, town: town, monsterName: monsterName)
}

And to test the code:

/// main.swift
var failableMonster = Monster(town: myTown, monsterName: "")
print(failableMonster?.name) // -> prints nil
#2

Regarding the Silver challenge, I did the exact same thing as you:

convenience override init(town: Town?, monsterName: String) {
    self.init(limp: false, fallingApart: false, town: town, monsterName: monsterName)
  }

I am guessing that because init functions don’t have names, they are only identifiable by their default designations - that is by their parameters. And that is way override is necessary, because otherwise there is no way to distinguish the class init from the superclass. Is this right?

#3

re silver challenge - in the book it says …
“Unlike other functions that you must override if you inherit them from your superclass, you do not mark required initializers with the override keyword. It is implied by marking the initializer with required.”

Excerpt From: Mathias,Matthew. “Swift Programming: The Big Nerd Ranch Guide.” iBooks.

so I did this
required convenience init(town: Town?, monsterName: String){

    self.init(limp:false, fallingApart:false, town:town, monsterName: monsterName)
}

which seems to work fine

#4

Cheers man, this topic was very difficult and your answer helped a lot.

#5

I have pretty much the same answer as you do except I put convenience before required and the compiler didn’t complain. So apparently order of these keywords doesn’t matter.

I am curious what the convention is for the order of these keywords is though.

#6

Here is my solution to the Gold Challenge:

Monster.swift
Used guard monsterName != "" to make sure it’s not an empty String (anything else is fine):

    required init?(town: Town?, monsterName: String) {
        guard monsterName != "" else {
            return nil
        }
        self.town = town
        name = monsterName
    }

Zombie.swift
Then make all the initializers failable by adding the ? to them. The convenience initializers are all dependent on the designated initializer, and the Zombie class’ designated initializer is dependent on the Monster class’ designated initializer(which is failable). so you have to make the Zombie class’ designated initializer failable as well:

    init?(limp: Bool, fallingApart: Bool, town: Town?, monsterName: String) {
        walksWithLimp = limp
        isFallingApart = fallingApart
        super.init(town: town, monsterName: monsterName)
    }
    
    convenience init?(limp: Bool, fallingApart: Bool) {
        self.init(limp: limp, fallingApart: fallingApart, town: nil, monsterName: "Fred")
        if walksWithLimp {
            print("This zombie has a bad knee.")
        }
    }
    
    convenience required init?(town: Town?, monsterName: String) {
           self.init(limp: false, fallingApart: false, town: town, monsterName: monsterName)
    }
#7

I like your approach on the gold challenge of counting characters. My solution just looked for a truly empty string. That generally works . . . unless you put a space in for the name:

 init?(town: Town?, monsterName: String) {
    guard monsterName != "" else {
        return nil
    }
    self.town = town
    name = monsterName 
}
#8

Better alternative:

monsterName.isEmpty == false
public struct String {
    ...
    /// A Boolean value indicating whether a string has no characters.
    public var isEmpty: Bool { get }
    ...
}
#9

#10

In Apple’s guide, The Swift Programming Language, you can find an example in the section titled “Designated and Convenience Initializers in Action”. They write override convenience init
so I assume that is the conventional order.

As an aside, I found it helpful to read that section since Chapter 17 of the BNR book barely touches on overriding initializers. That made the silver challenge difficult for me without reading more in Apple’s guide.

1 Like
#11

init ?(town: Town?, monsterName: String) {

guard monsterName != nil else {

return nil

}
self.town = town
name = monsterName
}