Search code examples
swiftinheritancepropertiesoverridinghierarchy

Inheriting ugly: Swift subclass alters superclass' view of own stored properties?


I have a subclass whose inheritance chain breaks down to look like this:

InputAccessoryEnabledTextField : UITextField : UIControl : UIView : UIResponder

InputAccessoryEnabledTextField provides an override:

private var myInputAccessoryController: UIInputViewController?

override var inputAccessoryViewController: UIInputViewController? {
    get { myInputAccessoryController }
    set { myInputAccessoryController = newValue }
}

The code above, working as the solution I was seeking, is from the accepted answer (@Sweeper) to a question I just asked on S.O. It is overriding an instance property of UIResponder.

However, it doesn't make sense to me. How can/does it work?

How is it possible that UITextField, superclass to my subclass, honors an override provided my subclass (InputAccessoryEnabledTextField)?

Doesn't that violate the inheritance hierarchy? Shouldn't only subclasses of InputAccessoryEnabledTextField be able to see its override, not superclasses?

Or do overrides apply to the whole object, such that every inherited superclass sees the state of some arbitrary outermost subclass? Or, is it that the iOS text subsystem is doing some really convoluted stuff?

Maybe this is too abstract a question for S.O. and I don't mind closing or deleting it, Just posting this to avoid a 'dialog' in the comments that the bot complains about.

  • Note: I don't find much clarity about it in Inheritence chapter of Swift 5 documentation *

Solution


  • The following example code demonstrates that a Swift superclass experiences its own properties through outermost subclass overrides!

    (The example below proves @RobNapier correct, which I initially confirmed by successfully overriding UIResponder.inputAccessoryViewController and observing my viewController activated when the keyboard pops up for my subclassed UITextView : UIResponder)

    The Good:

    Swift overrides as explained by @RobNaipier in comments, make sense, at least from certain points of view. And can obviously be exploited for its interesting flexibility

    The Bad:

    However, it isn't what I assumed, and I was somewhat stunned that inheritance works that way, because intuitively I realized that letting subclasses tamper with superclasses` view of themselves is potentially risky, especially if one doesn't know superclass implementation details (as is the case with UIKits proprietary implementation code Apple doesn't release the source to the public).

    The Ugly:

    So while Swift inheritance lets the inheritors achieve tweak things for interesting or useful effect, and could be very handy in some cases, in practical use, for example with UIKit, it does leads to anticipated problems and confusion.

    The coup de grâce, which I'm grateful that Rob pointed out, is that, due to the anticipated downsides, class inheritance with UIKit is increasingly discouraged by Apple and struct+protocol has been adopted by SwiftUI.

    class TheSuperclass {
    
        var x = 5
    
        init() {
            print("How superclass sees it before subclass  initialized: x = \(x)")
        }
    
        func howSuperclassSeesItselfAfterSubclassInit() {
            print("How superclass sees it after subclass   initialized: x = \(x)")
        }
    }
    
    class TheSubclass : TheSuperclass {
    
        override var x : Int {
            get { super.x + 10 }
            set { super.x = newValue }
        }
    
        override init() {
            super.init()
            print("How subclass   sees it after superclass" + 
                  "initialized: x = \(x), super.x = \(super.x)")
        }
    
    }
    
    TheSubclass().howSuperclassSeesItselfAfterSubclassInit()
    

    The above code when run in Playground displays the following:

    How superclass sees it before subclass  initialized: x = 5
    How subclass   sees it after superclass initialized: x = 15, super.x = 5
    How superclass sees it after subclass   initialized: x = 15