简体   繁体   English

Swift 中的协议向下转换

[英]Downcasting on protocols in Swift

I have the following code:我有以下代码:

protocol Step { /* body */ }

enum StepA: Int, CaseIterable, Step {
    case one
    case two
    case three
}

enum StepB: Int, CaseIterable, Step {
    case one
    case two
    case three
}

protocol Component {
    var step: Step { get }
}

protocol ComponentA: Component {
    var step: StepA { get }
}

protocol ComponentB: Component {
    var step: StepB { get }
}


struct SomeComponentOfTypeA: ComponentA {
    var step: StepA = StepA.one
}


let a = SomeComponentOfTypeA()

print(a.step)

The type of a.step I would like to be StepA , since I am initalizing this way. a.step的类型我想成为StepA ,因为我正在以这种方式初始化。 But, I can't do that because:但是,我不能这样做,因为:

struct SomeComponentOfTypeA: ComponentA {
    var step: StepA = StepA.one
}

the compiler tells me that:编译器告诉我:

Type 'SomeComponentOfTypeA' does not conform to protocol 'Component'类型“SomeComponentOfTypeA”不符合协议“组件”

Basically, it doesn't know that since I am implementing the ComponentA , my step should be of type StepA too.基本上,它不知道因为我正在实现ComponentA ,所以我的步骤也应该是StepA类型。

As a workaround I found that if I modify this part:作为一种解决方法,我发现如果我修改这部分:

protocol Component {
    var step: Step { get }
}

into:进入:

protocol Component {
    associatedtype SomeStep: Step
    var step: SomeStep { get }
}

Everything works just fine, a.step is of StepA , no issues at all...一切正常, a.step属于StepA ,完全没有问题......

Question: why is this working?问题:为什么这有效? I am basically hiding the fact that step property from Component is of type Step , by using the associated type SomeStep .我基本上是通过使用关联类型SomeStep来隐藏Componentstep属性是Step类型的事实。 This is kinda weird, isn't it?这有点奇怪,不是吗? I was expecting this to work just fine without having to hide anything, why is this happening?我希望这可以正常工作而不必隐藏任何东西,为什么会发生这种情况? How does it work?它是如何工作的?

Thanks.谢谢。 Any help would be much appreciated!任何帮助将非常感激!

Do you really need all the protocols?你真的需要所有的协议吗? You could do this with associated values:您可以使用关联值执行此操作:

protocol Step { /* body */ }

enum StepA: Int, CaseIterable, Step {
    case one
    case two
    case three
}

enum StepB: Int, CaseIterable, Step {
    case one
    case two
    case three
}

enum Component {
    case typeA(StepA)
    case typeB(StepB)
}

let component = Component.typeA(StepA.one)

The mistake is in the way the protocols are defined.错误在于协议的定义方式。

  1. Protocol Component has a variable step of type Step (a protocol) Protocol Component 有一个Step类型的变量step (一个协议)
  2. The variable step gets redeclared in protocol ComponentA变量step在协议 ComponentA 中重新声明
  3. So when defining SomeComponentOfTypeA the compiler gives an error as it finds step of type StepA per ComponentA but not of type Step per Component protocol.因此,在定义SomeComponentOfTypeA时,编译器会给出一个错误,因为它发现每个ComponentAStepA类型的step但不是每个Component协议的Step类型的 step。
  4. When you define AssociatedType, you then make use of generics to specify that step variable in Component can be of any type following protocol Step定义 AssociatedType 时,然后使用 generics 来指定Component中的step变量可以是遵循协议Step的任何类型
  5. So now ComponentA's step variable satisfies point 4 and hence no error.所以现在 ComponentA 的 step 变量满足第 4 点,因此没有错误。

And therefore, what you did finally using generics is the right approach for such a case, if you really wanted to redeclare the variable step in ComponentA for a specific type StepA .因此,如果您真的想在ComponentA中为特定类型StepA重新声明变量step ,那么您最终使用 generics 所做的就是这种情况的正确方法。

Protocol can be adopted by a struct or enum so it don't do class polymorphism, because they can not be inherited.结构或枚举可以采用协议,因此它不会执行 class 多态性,因为它们不能被继承。 This is also one of the reasons that why associatedtype exists.这也是为什么会有associatedtype存在的原因之一。

Back to your question, you defined a variable with the same name as the parent protocol in the sub-protocol, but the type is different, this is allowed because the protocol is not a concrete type.回到您的问题,您在子协议中定义了一个与父协议同名的变量,但类型不同,这是允许的,因为协议不是具体类型。 But this is not a type overload, because the protocol works differently from the class.但这不是类型重载,因为该协议的工作方式与 class 不同。

In this case, you'd better use generics, which is associatedtype .在这种情况下,您最好使用 generics,即associatedtype类型。 But if you have to use these three protocols, you only need to declare the sub-protocol.但是如果非要使用这三个协议,只需要声明子协议即可。

...

protocol ComponentA: Component {}

protocol ComponentB: Component {}

struct SomeComponentOfTypeA: ComponentA {
  var step: Step = StepA.one
}

struct SomeComponentOfTypeB: ComponentB {
  var step: Step = StepB.two
}

let a = SomeComponentOfTypeA()

print(a.step)
print(type(of: a))

let b = SomeComponentOfTypeB()

print(b.step)
print(type(of: b))

Output: Output:

one
SomeComponentOfTypeA
two
SomeComponentOfTypeB

Another way is to meet your needs.另一种方法是满足您的需求。 Limit SomeComponentOfTypeA only allows StepA type.限制SomeComponentOfTypeA只允许StepA类型。

protocol StepAProtocol { /* body */ }
protocol StepBProtocol { /* body */ }

enum StepA: Int, CaseIterable, StepAProtocol {
    case one
    case two
    case three
}

enum StepB: Int, CaseIterable, StepBProtocol {
    case one
    case two
    case three
}

protocol Component {
  associatedtype T
  var step: T { get }
}

protocol ComponentA: Component where T: StepAProtocol  {}
protocol ComponentB: Component where T: StepBProtocol  {}

struct SomeComponentOfTypeA: ComponentA {
  var step: StepA = .one
}

struct SomeComponentOfTypeB: ComponentB {
  var step: StepB = .two
}

struct SomeComponentOfTypeC: ComponentB {
  var step: StepA = .two // error: Type 'StepA' does not conform to protocol 'StepBProtocol'
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM