Home > Back-end >  Inheriting ugly: Swift subclass alters superclass' view of own stored properties?
Inheriting ugly: Swift subclass alters superclass' view of own stored properties?

Time:06-19

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 *

CodePudding user response:

In short

This is indeed the overriding of properties. Swift deals with properties by generating code that is equivalent to accessing properties via a via getters and setters. This allows to override a property by overriding the getter and the setter.

More explanations

Your snippet is overriding of a property

In your code, InputAccessoryEnabledTextField indirectly inherits from UIResponder, which has an existing property inputAccessoryViewController.

Your code snippet defines a new private property myInputAccessoryController, and uses it in the overriding of the inherited property inputAccessoryViewController, and more precisely, the overriding of its getter and setter.

Purpose in the case of your snippet

In fact, the purpose of this overriding is even explained in the documentation of inputAccessoryViewController:

The value of this read-only property is nil.

But what's the use of a property that is real only and returns only nil?

If you want to attach custom controls to a system-supplied input view controller (such as the system keyboard) or to a custom input view (...), redeclare this property as read-write in a UIResponder subclass.

How can property overriding even work?

While property overriding may seem weird at the first sight, we realize that this is just the normal overriding mechanism once we have understood that:

The stored or computed nature of an inherited property isn’t known by a subclass—it only knows that the inherited property has a certain name and type. You must always state both the name and the type of the property you are overriding, to enable the compiler to check that your override matches a superclass property with the same name and type.

Here we see the power of Swift's properties. You can make any property public, and still benefit from encapsulation and specialization, overriding it like functions. The explanation is that a property has two faces:

  • the class-internal implementation details: is the property stored or computed ?
  • the implicit class interface for the external world, including for subclasses: the outside world use the getter and the setter. These can be overridden.
  • the same principle works for property observers such as didSet: you can override them even if the base class didn't define any special behavior for them.

Here a small unrelated but extreme toy example to illustrate this feature (I would not recommend its design ;-) ):

class Lense {
    init (opticalZoom: Int) {
        magnifyingFactor = opticalZoom
    }
    // stored property
    // And has implicitly a getter and a setter 
    var magnifyingFactor : Int = 2
}

class ElectronicLense : Lense {
    // overriden property
    // it overrides getter and setter, and uses the propery of the super-class to store the value
    override var magnifyingFactor: Int {
        get { super.magnifyingFactor * 5 }
        set { super.magnifyingFactor = newValue / 5 }
    }
    // pass through property
    var opticalFactor : Int {
        get {super.magnifyingFactor}
    }
}
    
var le = ElectronicLense(opticalZoom: 3)

print ("Zoom: \(le.magnifyingFactor) with an optical factor \(le.opticalFactor)")

CodePudding user response:


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
  • Related