独立开花卓富贵

Swift 5.6 新特性:existential any

Swift 5.6 中引入了一个新关键字 any (注意不是 Any 类型)。使用的方式和 some 有点像,都是修饰一个类型。

protocol Networking {
   func sendPost()
}

struct Server {
   let networking: any Networking
}

所有的 existential 类型前面都需要使用 any 修饰。在 Swift 5.6 中会出现警告,在 6.0 大版本中会强制要求,是一个 breaking change。

从结果上看就是要求所有持有的属性类型是 Protocol(非泛型约束)增加了一个声明的语法。注意不是所有的 Protocol 都需要使用  any 修饰。为什么需要这样做呢?因为在编译层面:泛型约束的 Protocol 和常规的 Protocol 属性是两个完全不同的类型。

以下面的代码举例:

protocol Networking {
   func sendPost()
}

class NormalHTTP: Networking {
   func sendPost() {
       //....
   }
}

struct Server {
// 未知类型,类型已经被擦除,可以是任何一个类型
   let networking: Networking
}

struct ServerGeneric<T: Networking> {
// 在编译的时候可以确定 T 的具体类型
   let networking: T
}

let instance = NormalHTTP()
let dataInstance = Server(networking: instance)
let serverInstance = ServerGeneric<NormalHTTP>(networking: instance)

如果从结果上看,上面的 Server 和 ServerGeneric 的效果是一样的,都持有了一个 Networking 实例。但是这两个实现的成本确是不同的。

直接声明 Protocol 的属性在编译的时候是 existential 类型,没有类型信息。没有类型信息就意味着可能是值类型,也可能是引用类型。无法确定内存大小。这样就必然要有一个类包一层。因为没有了类型,在方法调用的时候编译器也无法优化,只能在运行的时候动态调用,性能也差很多。关于昂贵的成本也可以参考 Swift 团队的文档里的话:

Existential types are also significantly more expensive than using concrete types. Because they can store any value whose type conforms to the protocol, and the type of value stored can change dynamically, existential types require dynamic memory unless the value is small enough to fit within an inline 3-word buffer. In addition to heap allocation and reference counting, code using existential types incurs pointer indirection and dynamic method dispatch that cannot be optimized away.

相比有泛型约束的 Protocol 在编译的时候就可以确定类型了,和普通声明一个具体类型的成本是一样的。

因为这两种声明在本质上是完全不一样的,但是原来的语法设计容易让开发者意识不到两者的区别。所以这次在语法上做出改变,让开发者去发现这两种声明的区别。希望开发者们可以不去滥用 any ,写出性能更好的代码。

参考

SE-0335: Introduce existential any

What is the “any” keyword in Swift?

What’s new in Swift 5.6

Swift 关联类型(续)