В Swift, как передавать в протокол со связанным типом?
В следующем коде я хочу проверить, является ли x
SpecialController
. Если это так, я хочу получить currentValue
как SpecialValue
. Как ты делаешь это? Если не с литой, то другой метод.
Последняя строка не будет компилироваться. Ошибка: Протокол "SpecialController" может использоваться только в качестве общего ограничения, поскольку он имеет собственные или связанные требования типа.
protocol SpecialController {
associatedtype SpecialValueType : SpecialValue
var currentValue: SpecialValueType? { get }
}
...
var x: AnyObject = ...
if let sc = x as? SpecialController { // does not compile
Ответы
Ответ 1
К сожалению, Swift в настоящее время не поддерживает использование протоколов со связанными типами в качестве реальных типов. Однако это технически возможно для компилятора; и это вполне может быть реализовано в будущей версии языка.
Простое решение в вашем случае состоит в том, чтобы определить "теневой протокол", из которого создан SpecialController
, и позволяет вам получить доступ к currentValue
через требование протокола, тип которого стирает его:
// This assumes SpecialValue doesn't have associated types – if it does, you can
// repeat the same logic by adding TypeErasedSpecialValue, and then using that.
protocol SpecialValue {
// ...
}
protocol TypeErasedSpecialController {
var typeErasedCurrentValue: SpecialValue? { get }
}
protocol SpecialController : TypeErasedSpecialController {
associatedtype SpecialValueType : SpecialValue
var currentValue: SpecialValueType? { get }
}
extension SpecialController {
var typeErasedCurrentValue: SpecialValue? { return currentValue }
}
extension String : SpecialValue {}
struct S : SpecialController {
var currentValue: String?
}
var x: Any = S(currentValue: "Hello World!")
if let sc = x as? TypeErasedSpecialController {
print(sc.typeErasedCurrentValue as Any) // Optional("Hello World!")
}
Ответ 2
[Отредактировано для исправления: : SpecialValue
, not = SpecialValue
]
Это невозможно. SpecialValueController
концепт "неполный тип", поэтому компилятор не может знать. SpecialValueType
, хотя он ограничен SpecialValue
, он неизвестен до тех пор, пока он не будет определен любым принятым классом. Так что это действительно заполнитель с неадекватной информацией. as?
-ness не может быть проверен.
У вас может быть базовый класс, который принимает SpecialController
с конкретным типом для SpecialValueController
и имеет несколько дочерних классов, которые наследуют от принимающего класса, если вы все еще ищете степень полиморфизма.
Ответ 3
Это не работает, потому что SpecialController
не является одним типом. Вы можете думать о связанных типах как о разновидности. A SpecialController
с его SpecialValueType
, являющимся Int
, является совершенно другим типом от SpecialController
с его SpecialValueType
, являющимся String
, точно так же, как Optional<Int>
является совершенно другим типом от Optional<String>
.
Из-за этого нет смысла бросать на SpecialValueType
, потому что это замаскирует связанный тип и позволит вам использовать (например) a SpecialController
с его SpecialValueType
, являющимся Int
, где ожидается a SpecialController
с его SpecialValueType
, равным String
.
Как говорит компилятор, единственный способ SpecialController
можно использовать как общее ограничение. У вас может быть функция с общим значением T
с ограничением T
должно быть SpecialController
. Область T
теперь охватывает все различные конкретные типы SpecialController
, такие как один с ассоциированным типом Int
и один с String
. Для каждого возможного связанного типа существует четкий SpecialController
и по расширению отдельный T
.
Вычеркивать аналогию Optional<T>
. Представьте себе, возможно ли то, что вы пытаетесь сделать. Это было бы так:
func funcThatExpectsIntOptional(_: Int?) {}
let x: Optional<String> = "An optional string"
// Without its generic type parameter, this is an incomplete type. suppose this were valid
let y = x as! Optional
funcThatExpectsIntOptional(y) // boom.