Быстрая отправка в переопределенные методы в расширениях подкласса

переопределение меток в расширениях, по-видимому, приводит к непредсказуемым результатам в некоторых случаях. Следующий пример демонстрирует два разных результата с похожим шаблоном.

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()

Отпечатки:

dothing super class
viewdidload subclass
viewdidload superclass

Вы можете увидеть, что это пропускает реализацию B doThing, когда она отлита как A, однако включает обе версии viewDidLoad при передаче как UIViewController. Это ожидаемое поведение? Если да, то в чем причина этого?

ENV: Xcode 7.3, игровая площадка

Ответ 1

Удивительно, что компилятор разрешает переопределение в расширении. Это не компилируется:

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()
    }
}

В вашем примере, кажется, что компилятор дает вам пропуск, потому что A происходит от NSObject - предположительно, чтобы позволить этому классу взаимодействовать с Objective-C. Это скомпилирует:

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

Мое предположение заключается в том, что факт, что вы позволили сделать это переопределение вообще, сам по себе может быть ошибкой. docs говорят:

Расширения могут добавлять новые функции к типу, но они не могут переопределить существующие функции.

И переопределение нигде не упоминается как одна из вещей, которые может сделать расширение. Поэтому кажется, что это не должно компилироваться. Однако, возможно, это разрешено специально для совместимости с Objective-C, как я уже говорил. В любом случае, мы изучаем краевой кейс, и вы очень хорошо вызвали его отвращение.

В частности, предыдущий код по-прежнему не приводит к тому, что динамическая отправка становится работоспособной. Поэтому вам нужно объявить doThing как dynamic, как было предложено @jtbandes, или поместить его в фактический класс, а не в расширение - если вы хотите, чтобы полиморфизм работал. Таким образом, это работает так, как вы ожидаете:

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

И так:

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

Мое заключение было бы: очень хороший пример; отправьте его Apple в качестве возможной ошибки; и не делай этого. Сделайте свое переопределение в классе, а не в расширении.