Search code examples
swiftinitializationoverridingsubclasssuperclass

Struggling to Understand Swift 3 Overriding of Failable Initialisers with Nonfailable Initialisers


I'm trying to learn the concept of overriding a failable initialiser in swift and have encountered the following statements:

the only way to delegate up to the superclass initializer is to force-unwrap the result of the failable superclass initializer.

The textbook did not provide any code to really explain what it really means? Could someone please kindly explain it to me? Even better if it comes with an code example!


Solution

  • I have to admit, the section "Overriding a Failable Initialiser" is quite confusing.

    The following example should clarify this scenario:

    Suppose, you have a base class with a failable initialiser:

    class Base {
        let name: String
    
        init?(name: String) {
            guard !name.isEmpty else {
                return nil
            }
            self.name = name
        }
    }
    

    Note that a failing initialiser returns an Optional.

    Here, the initialiser requires that you pass a non-empty string, otherwise the initialiser "fails" - that is, it returns an optional whose value is nil (respectively .None).

    Now, let's define a class that derives from Base. In the first version, this will not compile, tough!

    class Derived: Base {
        let age: Int
    
        init(name: String, age: Int) {
            self.age = age
            super.init(name: name) //<- error 
        }
    }
    

    The compiler issues the following error:

    error: a non-failable initializer cannot chain to failable initializer 'init(name:)' written with 'init?'
            super.init(name: name)
                  ^
    

    The problem here is, that the non-failing initialiser of the subclass delegates to the failing base class initialiser.

    We have two options to correct the issue:

    1. Force unwrap the failable initialiser:

    class Derived: Base {
        let age: Int
    
        init(name: String, age: Int) {
            self.age = age
            super.init(name: name)!   // <- force unwrap 
        }
    }
    

    The caveat with this solution is, that if you pass an empty name to the subclass initialiser, e.g.

    let derived = Derived(name: "", age: 12)
    

    it results in a fatal error when trying the force unwrap the optional from the base class initialiser:

    fatal error: unexpectedly found nil while unwrapping an Optional value
    

     2. Making the subclass initialiser failable as well:

    class Derived: Base {
        let age: Int
    
        init?(name: String, age: Int) {  // use failable initialiser
            self.age = age
            super.init(name: name)  // <- propagate the failure with init?
        }
    }
    

    This solution simply propagates the nil result from the base class initialiser to the caller - which makes the caller responsible to handle the optional appropriately.