#swift #generics #swift-protocols
#swift #обобщения #swift-протоколы
Вопрос:
Есть ли способ определить универсальную функцию в протоколе и разрешить соответствующему объекту определять специализации для этого протокола? Например:
protocol Generic {
func generic<T>(prop: T, otherProp: String)
}
class Gen: Generic {
func generic<T>(prop: T, otherProp: String) {
print("generic")
}
func generic(prop: String, otherProp: String) {
print(prop)
}
}
Теперь, если я использую класс следующим образом:
let inst = Gen()
inst.generic(prop: 1, otherProp: "Go")
inst.generic(prop: "Hello", otherProp: "Stop")
Я получаю ожидаемый результат:
generic
Hello
Однако, если я объявлю, что inst
он имеет тип Generic
:
let inst: Generic = Gen()
inst.generic(prop: 1, otherProp: "Go")
inst.generic(prop: "Hello", otherProp: "Stop")
Я получаю:
generic
generic
Итак, если у меня есть свойство типа Generic
, я не могу использовать специализацию универсальной функции от разработчика протокола. Это ожидаемое поведение? Есть ли способ добиться поведения, которое я ищу, т. Е. использовать специализацию универсальной функции даже при доступе через интерфейс протокола? Я был бы признателен за любое понимание этого. Спасибо всем.
Комментарии:
1. У Фелиппа есть ответ, но я был бы крайне осторожен с этим. Его следует использовать только тогда, когда вы можете обеспечить улучшение производительности. Эти реализации имеют разное поведение , это очень хрупкий и сложный в отладке протокол, а правила могут быть довольно тонкими (например, если вы вызываете другой универсальный метод, который затем вызывает это, вы можете потерять специализацию). В принципе, если у вас есть
f<T>(value: T)
иf(value: String)
, они должны иметь одинаковое видимое поведение для String; последнее просто может быть более эффективным.
Ответ №1:
Если вы объявите требование протокола как универсальную функцию, вы не сможете вызвать более специализированную перегруженную версию той же функции через тип протокола. Однако вы можете специализировать реализацию универсальной функции для вашего принимающего класса, проверив тип универсального входного аргумента.
class Gen: Generic {
func generic<T>(prop: T, otherProp: String) {
if prop is String {
print(prop)
} else {
print("generic")
}
}
}
let inst: Generic = Gen()
inst.generic(prop: 1, otherProp: "Go")
inst.generic(prop: "Hello", otherProp: "Stop")
Комментарии:
1. В итоге это то, что я закончил делать. Наверное, я надеялся, что что-то пропустил или что кто-то может определенно сказать мне, что это ожидаемое поведение. Я думаю, что могу попытаться создать проблему в проекте swift и посмотреть, можно ли добавить функциональность, которую я ищу. Тем не менее, спасибо за ваш ответ.
2. @dudeman даже если бы это было технически возможно, я не думаю, что эта функциональность будет или должна быть реализована. Перегрузка требования протокола в соответствующем классе не должна приводить к вызову этой перегруженной версии при доступе через тип протокола, поскольку перегруженная версия не объявлена в самом протоколе. Если в вашем примере вы используете значение
inst
toGen
, то вы можете напрямую вызвать перегруженную версию.
Ответ №2:
Вы могли бы добавить сигнатуру метода generic(String, String)
в протокол и добавить реализацию по умолчанию с расширением:
protocol Generic {
func generic<T>(prop: T, otherProp: String)
func generic(prop: String, otherProp: String)
}
extension Generic {
func generic(prop: String, otherProp: String) {
generic(prop: prop as Any, otherProp: otherProp)
}
}
Комментарии:
1. Я рассматривал решение, подобное этому, но большая проблема, которую я вижу в этом решении, заключается в том, что для того, чтобы я мог добавить специализацию к типу, который соответствует протоколу, я должен изменить протокол. Если у меня нет возможности модифицировать протокол, это не сработает. И, в целом, я бы сказал, что изменять интерфейс в соответствии с конкретной реализацией — плохая практика. Тем не менее, спасибо за предложение.