Search code examples
iosswiftuiviewcontrollerdesignated-initializer

Why must I keep declaring the same required but not implemented initializer for init(coder aDecoder) for my programatic UIViewController subclass?


Perhaps it is just me, but I find certain aspects of swift... obtuse to say the least.

I don't use Interface Builder most of the time because I like using PureLayout. So I was hoping to make a UIViewController subclass, say PureViewController, that had a handy init without parameters:

class PureViewController : UIViewController {

    init() {
        super.init(nibName: nil, bundle: nil)
    }

}

But this is not okay, for XCode tells me I must also implement init(coder aDecoder: NSCoder). Okay, that's fine! That's why I made this class - so I don't have to do this again for subclasses.

class PureViewController : UIViewController {

    init() {
        super.init(nibName: nil, bundle: nil)
    }

    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

Ok, now here's what I don't get.

I define a subclass, SomePureViewController : PureViewController, with an initializer init(viewModel:ICrackersViewModel)...

class SomePureViewController : PureViewController {

    init(viewModel:ICrackersViewModel) {
        super.init()
    }

}

But it STILL wants me to define the same stupid initializer till kingdom come!

class SomePureViewController : PureViewController {

    init(viewModel:ICrackersViewModel) {
        super.init()
    }

    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

Now I understand the idea - there is no init(decoder) in my subclass, even though it is defined in its parent class.

Maybe I've always dealt with this issue with UIViewController and never noticed it before.

My questions are as follows:

  1. Is there something I am doing wrong to cause this behavior?
  2. Is there any way outside of inheritance that I can avoid repeating myself?
  3. Are there plans to any plans to change this?

Solution

  • The point is that one can initialize a possibly derived class just by knowing the base type.

    Lets assume a base class

    class Base {
        let value: Int
    
        required init(value: Int) {
            self.value = value
        }
    }
    

    and a function

    func instantiateWith5(cls: Base.Type) -> Base {
        return cls.init(value: 5)
    }
    

    then we can do

    let object = instantiateWith5(Base.self)
    

    Now if someone defines a derived class

    class Derived: Base {
        let otherValue: Int
    
        init() {
            otherValue = 1
            super.init(value: 1)
        }
    
        required init(value: Int) {
            fatalError("init(value:) has not been implemented")
        }
    }
    

    We are at least able to call

    let object2 = instantiateWith5(Derived.self)
    

    violating LSP, but that's a problem of your code, not the language.

    Swift has to guarantee that initializers leave your objects in a initialized state, even when they are derived from a base object, so I guess changing that would be a bad thing. If you like to define a UIViewController that is not deserializable and thereby violating LSP it's your decision - but don't expect the language to support you in this.