Swift: Type 'Protocol' does not conform to protocol 'Protocol'?

265 阅读3分钟

起因

今天在写Swift的时候,遇到了一个奇怪的bug,代码大致是这样的:

protocol A {

}

protocol B: A {}

class C: B {}

protocol Test {
    associatedType AProtocol: A
    var a: AProtocol { get }
}

class TestImpl<T: A>: Test {
    var a: T

    init(a: T) {
       self.a = a
    }

}

let c: B = C()

let test = TestImpl(a: c)

上面的代码想要做的事情很简单,即将TestImpl只依赖ProtocolA而不依赖于它的具体实现 但Swift compiler却抛出一个错误:Type 'any B' cannot conform to 'A'

这我就纳闷了,B不是继承了A的吗?

于是我将代码改成了:

let c: A = C()

let test = TestImpl(a: c)

结果 Swift compiler 继续抛出Type 'any A' cannot conform to 'A'

惊了,A 居然连它自己都不conform?接下来就让我们一探究竟

Swift中的类型机制

写过Swift的人都或多或少的知道Protocol这个概念,它定义了一组行为,其中包括了构建器(constructor)、静态属性和方法。 他最主要的用处是,在声明一个值的类型时,可以把它声明为这个Protocol的类型

但和其他语言不同的是,它是不能被当作一个具体类型来使用

什么是使用呢?在Swift中,除了声明的时候,其他任何时候用到的类型可以都认为是使用这个类型,比如:

  • 当传递参数的时候
    foo(a: aImpl) // aImpl 的 类型是被<使用>了
    
  • 当声明泛型的类型时
    foo<A>() // 类型A被<使用>了
    

注意,被使用的类型必须是一个具体的类型,也就是说,这个类型不能是一个Protocol:

protocol A {}
func foo(a: A) {}

let aImpl: A = ... // 一个A的具体实现
foo(a: aImpl) // X, 使用A是不被允许的

Swift为什么要这样设计?

感兴趣的朋友可以看一下这个Protocol doesn't conform to itself?

我认为这里最能说服我的一点即:

  • Protocol能声明构造函数,如果它能够被当作具体类型来使用的话,那么当我调用它的构造函数时,Swift怎么知道我要构建哪一个类型的instance呢?
protocol A {
    init()
}

class B: A {
    init()
}

class C: A {
    init()
}

class Test<T:A> {
    func foo() -> T {
        return T() // 如果swift只知道T是A的话,那么我怎么知道要调用B的init还是C的init?
    }
}

PS: 这里我其实有点疑惑,为什么Swift要允许Protocol能加入init和静态属性方法的声明,这导致这个Protocol使用时的限制大的离谱,到现在我还没有找到一个合适优雅的方式进行mock

解决办法

了解了Swift的类型机制后,那么我们就可以解答上面遇到的问题了。

let c: B = C()

let test = TestImpl(a: c)

上面的错误是因为我们把c声明成了一个Protocol类型,然后将它作为参数传给了TestImpl的构造函数,这致使我们使用了Protocol类型,是不被允许的

既然这是语言上的限制,最好的办法就是遵循它,只使用具体类型,即

let c = C() // 编译器自动识别 c 为 C

let test = TestImpl(a: C)

当然,也有绕开它的方式,但是不推荐,具体方法可以参考Protocol doesn't conform to itself?中的回答