ahtierney ahtierney - 6 months ago 29
iOS Question

Swift dispatch to overridden methods in subclass extensions

overriding method signatures in extensions seems to produce unpredictable results in certain cases. The following example demonstrates two different results with a similar pattern.

class A: UIViewController {
func doThing() {
print("dothing super class")
}

override func viewDidLoad() {
print("viewdidload superclass")
super.viewDidLoad()
}
}

class B: A { }

extension B {
override func doThing() {
print("dothing sub class")
super.doThing()
}

override func viewDidLoad() {
print("viewdidload subclass")
super.viewDidLoad()
}
}

let a: A = B()
a.doThing()

let vc: UIViewController = B()
vc.viewDidLoad()


This prints :

dothing super class
viewdidload subclass
viewdidload superclass


You can see this skips the
B
's implementation of
doThing
when it is cast as
A
, however includes both implementations of
viewDidLoad
when cast as
UIViewController
. Is this the expected behavior? If so, what is the reason for this?

ENV: Xcode 7.3, Playground

Answer

The surprise here is that the compiler permits the override in the extension. This doesn't compile:

class A {
    func doThing() {
        print("dothing super class")
    }
}
class B: A {
}
extension B {
    override func doThing() { // compile error
        print("dothing sub class")
        super.doThing()
    }
}

In your example, it appears that the compiler gives you a pass because A derives from NSObject — presumably in order to allow this class to interact with Objective-C. This does compile:

class A : NSObject {
    func doThing() {
        print("dothing super class")
    }
}
class B: A {
}
extension B {
    override func doThing() {
        print("dothing sub class")
        super.doThing()
    }
}

My guess is that the fact you're allowed to do this override at all is itself possibly a bug. The docs say:

Extensions can add new functionality to a type, but they cannot override existing functionality.

And overriding is nowhere listed as one of the things an extension can do. So it seems like this should not compile. However, perhaps this is permitted deliberately for compatibility with Objective-C, as I said before. Either way, we are then exploring an edge case, and you have very nicely elicited its edginess.

In particular, the preceding code still doesn't cause dynamic dispatch to become operational. That's why you either have to declare doThing as dynamic, as suggested by @jtbandes, or put it in the actual class rather than the extension — if you want polymorphism to operate. Thus, this works the way you expect:

class A : NSObject {
    dynamic func doThing() {
        print("dothing super class")
    }
}
class B: A {
}
extension B {
    override func doThing() {
        print("dothing sub class")
        super.doThing()
    }
}

And so does this:

class A : NSObject {
    func doThing() {
        print("dothing super class")
    }
}
class B: A {
    override func doThing() {
        print("dothing sub class")
        super.doThing()
    }
}

My conclusion would be: Very nice example; submit it to Apple as a possible bug; and Don't Do That. Do your overriding in the class, not in the extension.