在Swift中,如何将类型转换为具有关联类型的协议?

28
在以下代码中,我想测试x是否为特殊控制器SpecialController。如果是,我想将currentValue作为SpecialValue获取。你该如何做?如果不使用强制转换,则可以使用其他技术。
最后一行会出现编译错误。错误为:协议"SpecialController"只能用作通用约束,因为它具有Self或相关类型的要求。
protocol SpecialController {
    associatedtype SpecialValueType : SpecialValue
    var currentValue: SpecialValueType? { get }
}
...
var x: AnyObject = ...
if let sc = x as? SpecialController {  // does not compile

Swift 5.7: 如果让sc = x as? any SpecialController - john07
3个回答

28

很不幸,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!")
}

1

[编辑以修复::SpecialValue,而不是= SpecialValue]

这是不可能的。SpecialValueController 是一个“不完整类型”的概念,因此编译器无法知道。虽然SpecialValueType 受到SpecialValue 的约束,但在任何采用类确定之前,它都是未知的。因此,它只是一个带有不充分信息的占位符。无法检查as? 的性质。

如果您仍然希望实现一定程度的多态性,则可以使用采用SpecialController 的基类,并为SpecialValueController 使用具体类型,然后有多个继承自采用类的子类。


currentValue 是一个 SpecialValue 的事实是由任何采用它的类都无法更改的。你可以确保 currentValueSpecialValue 或其子类型。 - Rob N
@RobN 如果你不相信我的话,可以看看这个非常类似的问题,一个拥有320k声望值的人解释了同样的问题:http://stackoverflow.com/questions/39629358/why-is-this-causing-so-much-trouble-protocols-and-typealiases-on-their-associa/39629635#39629635 - BaseZen

0
这不起作用是因为SpecialController不是单一类型。您可以将关联类型视为一种泛型。一个其SpecialValueTypeIntSpecialController与其SpecialValueTypeStringSpecialController是完全不同的类型,就像Optional<Int>Optional<String>是完全不同的类型一样。
因此,将其强制转换为SpecialValueType是没有任何意义的,因为这会忽略关联类型,并允许您在期望SpecialValueTypeStringSpecialController的位置使用(例如)其SpecialValueTypeIntSpecialController

正如编译器所建议的那样,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.

1
在 Obj-C 中不是问题。 :) - nielsbot
@nielsbot,Objective-C 的弱类型离这里还很遥远 :p - Alexander
1
我并不是要和你们中的任何一个人争论Swift的想法。我只是在解释我的逻辑是合理的,所以似乎有一种方法可以用这种语言来表达。这就是为什么我可以在Java中使用运行时类型检查转换来实现它的原因。 - Rob N
@RobN 其实这并不是明智之举,Java 不应该允许这样做。唯一的原因是它的泛型系统是一个针对 Object 类型的编译时抽象。一旦你的通用代码被编译,底层都是 Object,具有无限制的运行时转换能力等。在 Swift 中,你可以通过使用 Any 来实现相同的功能... 但你不应该这样做。 - Alexander
@RobN,如果您同意“Array”是不完整的类型,那么“SpecialController”呢?如果我告诉您我有一个“SpecialController”的实例,我提供了足够的类型信息来解释“SpecialController”的所有方面行为了吗? - Alexander
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接