В 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.