Метод расширения протокола Swift вызывается вместо метода, реализованного в подклассе
Я столкнулся с проблемой, которая объясняется в коде ниже (Swift 3.1):
protocol MyProtocol {
func methodA()
func methodB()
}
extension MyProtocol {
func methodA() {
print("Default methodA")
}
func methodB() {
methodA()
}
}
// Test 1
class BaseClass: MyProtocol {
}
class SubClass: BaseClass {
func methodA() {
print("SubClass methodA")
}
}
let object1 = SubClass()
object1.methodB()
//
// Test 2
class JustClass: MyProtocol {
func methodA() {
print("JustClass methodA")
}
}
let object2 = JustClass()
object2.methodB()
//
// Output
// Default methodA
// JustClass methodA
Поэтому я ожидаю, что текст "SubClass methodA" должен быть напечатан после object1.methodB()
вызов. Но по какой-то причине реализация по умолчанию methodA()
из протокола расширение называется. тем не мение object2.methodB()
звонок работает как положено.
Это еще одна ошибка Swift в диспетчеризации метода протокола или я что-то упустил, и код работает правильно?
4 ответа
Именно так протоколы в настоящее время отправляют методы.
Таблица свидетелей протокола (см. Этот доклад WWDC для получения дополнительной информации) используется для динамической отправки к реализациям требований протокола при вызове на экземпляре с типом протокола. На самом деле это просто список реализаций функций для вызова каждого требования протокола для данного соответствующего типа.
Каждый тип, который заявляет о своем соответствии протоколу, получает свою собственную таблицу-свидетель протокола. Вы заметите, что я сказал "заявляет о своем соответствии", а не просто "соответствует". BaseClass
получает свою собственную таблицу свидетелей протокола для соответствия MyProtocol
, тем не мение SubClass
не получить собственную таблицу для соответствия MyProtocol
- вместо этого он просто полагается на BaseClass
"S. Если вы переместили: MyProtocol
вплоть до определения SubClass
, он получит свой собственный PWT.
Таким образом, все, о чем мы должны думать, это то, для чего PWT BaseClass
похоже. Ну, это не обеспечивает реализацию ни для одного из требований протокола methodA()
или же methodB()
- поэтому он опирается на реализации в расширении протокола. Это означает, что PWT для BaseClass
в соответствии с MyProtocol
просто содержит сопоставления с методами расширения.
Итак, когда расширение methodB()
метод вызывается и вызывает methodA()
он динамически отправляет этот вызов через PWT (так как он вызывается для экземпляра с типом протокола; self
). Поэтому, когда это происходит с SubClass
Например, мы проходим BaseClass
PWT. Таким образом, мы в конечном итоге вызываем расширение реализации methodA()
независимо от того, что SubClass
обеспечивает реализацию этого.
Теперь давайте рассмотрим PWT JustClass
, Это обеспечивает реализацию methodA()
следовательно, его PWT для соответствия MyProtocol
имеет эту реализацию в качестве отображения для methodA()
а также расширение реализации для methodB()
, Так когда methodA()
динамически отправляется через его PWT, мы в конечном итоге в его реализации.
Как я уже сказал в этом разделе " Вопросы и ответы", такое поведение подклассов, не получающих своих собственных PWT для протоколов, которым соответствуют их суперкласс (ы), действительно несколько удивительно и было воспринято как ошибка. Причиной этого, как говорит член команды Swift Джордан Роуз в комментариях к сообщению об ошибке, является
[...] Подкласс не может предоставить новых членов для удовлетворения соответствия. Это важно, потому что протокол может быть добавлен к базовому классу в одном модуле, а подкласс создан в другом модуле.
Поэтому, если бы это было поведение, у уже скомпилированных подклассов не было бы каких-либо PWT из соответствий суперкласса, которые были добавлены после факта в другом модуле, что было бы проблематично.
Как уже говорили другие, одним из решений в этом случае является BaseClass
обеспечить собственную реализациюmethodA()
, Этот метод теперь будет вBaseClass
PWT, а не метод расширения.
Хотя, конечно, потому что мы имеем дело с классами здесь, это будет не простоBaseClass
Реализация метода, который указан в списке - вместо этого он будет динамически отправлять через класс vtable (механизм, с помощью которого классы достигают полиморфизма). Поэтому для SubClass
Например, мы вызовем его переопределение methodA()
,
Очень короткий ответ, которым поделился со мной друг, был:
Только класс, который объявляет соответствие, получает таблицу-свидетель протокола.
Это означает, что подкласс, имеющий эту функцию, не влияет на настройку таблицы-свидетеля протокола.
Свидетель протокола — это контракт только между протоколом, его расширениями и конкретным классом, который его реализует.
Что ж, я предполагаю, что метод подкласса A не является полиморфным, потому что вы не можете поместить в него ключевое слово override, поскольку класс не знает, что метод реализован в расширении протокола, и, следовательно, не позволяет переопределить его. Метод расширения, вероятно, наступает на вашу реализацию во время выполнения, очень похоже на то, что 2 точных метода категории бьют друг друга с неопределенным поведением в цели C. Вы можете исправить это поведение, добавив еще один слой в вашу модель и реализовав методы в классе, а не в расширение протокола, таким образом получая из них полиморфное поведение. Недостатком является то, что вы не можете оставить методы невыполненными в этом слое, так как отсутствует встроенная поддержка абстрактных классов (что на самом деле вы пытаетесь сделать с расширениями протокола)
protocol MyProtocol {
func methodA()
func methodB()
}
class MyProtocolClass: MyProtocol {
func methodA() {
print("Default methodA")
}
func methodB() {
methodA()
}
}
// Test 1
class BaseClass: MyProtocolClass {
}
class SubClass: BaseClass {
override func methodA() {
print("SubClass methodA")
}
}
let object1 = SubClass()
object1.methodB()
//
// Test 2
class JustClass: MyProtocolClass {
override func methodA() {
print("JustClass methodA")
}
}
let object2 = JustClass()
object2.methodB()
//
// Output
// SubClass methodA
// JustClass methodA
Также релевантный ответ здесь: переопределение расширений протокола Swift
В вашем коде
let object1 = SubClass()
object1.methodB()
Вы вызвали methodB из экземпляра SubClass
, но SubClass
не имеет метода с именем methodB
, Однако его супер класс, BaseClass
соответствовать MyProtocol
, который имеет methodB
methodB.
Таким образом, он будет вызывать methodB
от MyProtocal
, Поэтому он выполнит methodA
в extesion MyProtocol
,
Чтобы достичь того, что вы ожидаете, вам нужно реализовать methodA
в BaseClass
и переопределить его в SubClass
, как следующий код
class BaseClass: MyProtocol {
func methodA() {
print("BaseClass methodA")
}
}
class SubClass: BaseClass {
override func methodA() {
print("SubClass methodA")
}
}
Теперь выходной станет
//Output
//SubClass methodA
//JustClass methodA
Хотя метод может достичь того, что вы ожидаете, но я не уверен, что этот вид структуры кода рекомендуется.