Диспетчеризация метода расширения протокола Swift с помощью суперкласса и подкласса
Я нашел интересное поведение, которое похоже на ошибку...
На основе поведения описаны следующие статьи:
https://medium.com/ios-os-x-development/swift-protocol-extension-method-dispatch-6a6bf270ba94
http://nomothetis.svbtle.com/the-ghost-of-swift-bugs-future
Выход не то, что я ожидаю, когда я добавляю SomeSuperclass
вместо непосредственного принятия протокола.
protocol TheProtocol {
func method1()
}
extension TheProtocol {
func method1() {
print("Called method1 from protocol extension")
}
func method2NotInProtocol() {
print("Called method2NotInProtocol from protocol extension")
}
}
// This is the difference - adding a superclass
class SomeSuperclass: TheProtocol {
}
// It works as expected when it simply adopts TheProtocol, but not when it inherits from a class that adopts the protocol
class MyClass: SomeSuperclass {
func method1() {
print("Called method1 from MyClass implementation")
}
func method2NotInProtocol() {
print("Called method2NotInProtocol from MyClass implementation")
}
}
let foo: TheProtocol = MyClass()
foo.method1() // expect "Called method1 from MyClass implementation", got "Called method1 from protocol extension"
foo.method2NotInProtocol() // Called method2NotInProtocol from protocol extension
Знаете ли вы, это ошибка или по замыслу? Коллега предположил, что смешивание наследования и расширений протокола может работать не так, как ожидалось. Я намеревался использовать расширение протокола, чтобы обеспечить реализацию по умолчанию... если я не могу сделать это, то мне, к сожалению, придется пометить его @objc
и вернитесь к дополнительному протоколу.
3 ответа
Из поста "Призрак стремительного будущего ошибок" приведены правила отправки расширений протоколов, которые упоминаются в конце поста.
- Если предполагаемый тип переменной является протоколом:
- И метод определен в исходном протоколе, ТО тогда вызывается реализация типа времени выполнения, независимо от того, есть ли реализация в расширении по умолчанию.
- И метод не определен в исходном протоколе, ТО вызывается реализация по умолчанию.
- В противном случае, если предполагаемый тип переменной является типом, то вызывается реализация типа.
Таким образом, вы говорите, что method1() определен в протоколе и реализован в подклассе. Но ваш суперкласс принимает протокол, но не реализует method1(), а подкласс просто наследуется от суперкласса и не принимает протоколы напрямую. Вот почему я считаю, что именно поэтому вы вызываете foo.method1(), он не вызывает реализацию подкласса, как указано в пунктах 1 и 2.
Но когда ты это сделаешь,
class SomeSuperclass: TheProtocol {
func method1(){
print("super class implementation of method1()")}
}
class MyClass : SomeSuperclass {
override func method1() {
print("Called method1 from MyClass implementation")
}
override func method2NotInProtocol() {
print("Called method2NotInProtocol from MyClass implementation")
}
}
а потом, когда ты позвонишь,
let foo: TheProtocol = MyClass()
foo.method1() // Called method1 from MyClass implementation
foo.method2NotInProtocol()
Так что обходной путь для этой ошибки (которая кажется ошибкой) заключается в том, что вам нужно реализовать метод протокола в суперклассе, а затем переопределить метод протокола в подклассе. НТН
Вариант, который не был рассмотрен, - разделить ваш протокол на несколько меньших, так что суперкласс не должен соответствовать протоколам, содержащим методы, которые он не намеревается реализовать. Подкласс может затем подписаться на эти другие протоколы отдельно.
Пожалуйста, проверьте код ниже:
import UIKit
protocol TheProtocol {
func method1()
func method2NotInProtocol()
}
extension NSObject {
func method1() {
print("Called method1 from protocol extension")
}
func method2NotInProtocol() {
print("Called method2NotInProtocol from protocol extension")
}
}
// This is the difference - adding a superclass
class SomeSuperclass :NSObject, TheProtocol {
override func method1() {
print("Called method1 from SomeSuperclass implementation")
}
}
// It works as expected when it simply adopts TheProtocol, but not when it inherits from a class that adopts the protocol
class MyClass : SomeSuperclass {
override func method1() {
print("Called method1 from MyClass implementation")
}
override func method2NotInProtocol() {
print("Called method2NotInProtocol from MyClass implementation")
}
}
let foo: TheProtocol = MyClass()
foo.method1() // expect "Called method1 from MyClass implementation", got "Called method1 from protocol extension"
foo.method2NotInProtocol() // Called method2NotInProtocol from protocol extension
Вместо того, чтобы писать расширение для TheProtocol, пишите расширение для абстрактного класса (NSObject в приведенном выше коде). Это работает как ожидалось.