问题

为什么协议可以当作类型使用,而带有关联类型的协议 (Protocol with associated type) 不能?

/// 编译通过
let foo: ContainerInt

/// 编译失败:Protocol 'Container' can only be used as a generic constraint because it has Self or associated type requirements
let bar: Container


protocol ContainerInt {
    mutating func append(_ item: Int)
}

protocol Container {
    associatedtype Item

    mutating func append(_ item: Item)
}

解答

声明变量将类型设为一个 Protocol 时,因为不知道具体类型,所以编译器会创建一个叫做 Existential Container 的中间类型。 Existential Container 会记录实际类型的 properties ,还有一个指向 Protocol Witness Table 的指针,可以找到该类型实现 Protocol 方法的实际位置。

普通 Struct 和 Existential Container 的内存检视


let foo = Stack()
let bar: ContainerInt = Stack()

print(MemoryLayout.size(ofValue: foo)) // 16 字节
print(MemoryLayout.size(ofValue: bar)) // 40 字节,Existential Container 40 字节

struct Stack: ContainerInt {
    let x = 1
    let y = 2

    mutating func append(_ item: Int) {}
}

但是编译器不能为 PAT(Protocol with associated type)生成 Existential Container,所以不能把 PAT 当成类型使用。

比如两个变量符合同一个协议,但是关联类型(associated type)有可能不一样,有一些协议方法就会有问题

 /// 如果允许 PAT,下面的写法会编译通过,然而 Int(15 的关联类型)和 String(“16” 的关联类型)是不能比较的。
 (15 as Comparable) < ("16" as Comparable)
 
 ///  Comparable 的定义,Self 也算一种关联类型
 public protocol Comparable : Equatable {
    static func < (lhs: Self, rhs: Self) -> Bool
 }

参考: