Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift dispatch to overridden methods in subclass extensions

Tags:

ios

swift

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

like image 284
ahtierney Avatar asked May 27 '16 22:05

ahtierney


1 Answers

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() { // error: declarations in extensions cannot override yet
        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.

like image 137
matt Avatar answered Sep 20 '22 17:09

matt