前言
在 Swift 开发中,经常会看到两个关键字,any 和 some,它们都用于协议,是两个看似相似但实际上有着重要区别的关键字。它们都与不确定的类型相关,但在使用场景和限制方面存在差异。今天我们将探讨 any 和 some 之间的区别,帮助你更好地理解它们在 Swift 中的作用。
any
我们先来探讨下这个关键字的作用,any 关键字是在 Swift 5.6 中引入的,在之前,代码封装的时候,可能会这么写:
protocol MyProtocol {
var name: String { get }
}
func myFunc(_ p: MyProtocol) {
print(p.name)
}
myFunc 函数接受一个参数 p,参数只要符合 MyProtocol 协议即可,没有别的要求。
这就导致传进来的参数可能是符合了协议的任何类型,也就意味着在编译阶段无法确定 p 是什么类型,在调用 name 的时候就无法通过静态调用来完成。
这种类型被称为存在类型(existential type)或者盒子类型(box type)。
相比直接传入固定的类型,这种类型有一些缺陷,而且存在类型也比使用具体类型有较大的开销,因为编译阶段无法确定其具体是什么类型,并且存储的值的类型可以动态更改,所以存在类型需要动态内存。除此之外使用这种类型还会导致无法使用动态方法调用优化。
对于上述情况带来的影响并不是很容易被人发现,因此 swift 开发社区提出在前面增加 any 关键字来标记这种情况,因此 myFunc 函数就变成了:
func myFunc(_ p: any MyProtocol) {
print(p.name)
}
现在清楚了吧,其实 any 只是用来标记这是一个盒子类型(box type),并无实际用处。但是需要注意使用 any 类型时,编译器对值的类型不会进行检查,这可能导致运行时错误。
目前 swift 没有强制使用 any 关键字,至少在 swift 6.0 之前都不会强制使用,但有一种情况例外,那就是如果协议里有关联类型(associatedtype),这个场景下使用的时候不加 any 会报错:
Use of protocol 'MyProtocol' as a type must be written 'any MyProtocol'
Replace 'MyProtocol' with 'any MyProtocol'
some
some 关键字是在 Swift 5.1 中引入的。它与协议一起使用来创建表示符合特定协议的内容的不透明类型(具体类型)。当在函数的参数位置使用时,意味着该函数正在接受某种符合特定协议的具体类型。
比如:
protocol MyProtocol {
var name: String { get }
}
func myFunc(_ p: some MyProtocol) {
}
myFunc 函数,接受一个符合 MyProtocol 协议的参数,some 用来表示参数是一个具体的 MyProtocol 类型。其实这在有 some 关键字之前就可以通过尖括号或者用 where 约束范性的方式实现:
func myFunc(_ p: some MyProtocol) {
print(p.name)
}
func myFunc1<T: MyProtocol>(_ p: T) {
print(p.name)
}
func myFunc2<T>(_ p: T) where T: MyProtocol {
print(p.name)
}
以上三个方法 myFunc、myFunc1、myFunc2 实际上是一样的,那么为什么已经能够实现相关的功能描述,还要新增关键字?其实是为了更方便的去表达这种情况,让语法看起来更简洁。
使用 some 关键字的作用在于告诉编译器我们正在处理特定的具体类型,因此在编译阶段就确定了类型,方法调用更高效。
any 和 some 的区别
上边单独介绍完了这两个关键字,可能还不是很容易理解,苹果的工程师曾经举过一个例子,我觉得比较形象。
any 标记的协议更像是一个“盒子”,如果需要知道具体是什么类型,需要先把盒子打开。因此编译期间并不知道具体是什么,需要运行时才知道。
some 标记的协议,是一个存在的类型,没有外层的“盒子”,编译期间就已经确定了类型。

根据这个特性,也很容易理解下边的例子了:
protocol MyProtocol {}
class MyC: MyProtocol {}
class MyC1: MyProtocol {}
var arr: [any MyProtocol] = [
MyC(),
MyC1(),
]
var arr1: [some MyProtocol] = [
MyC(),
MyC1(),
]
arr 的元素的类型为 any MyProtocol,其实编译器不会检查里边装的到底是什么类型,对编译器来说,只要是“盒子”就行了,因此能够顺利编译通过。
arr1 的元素的类型为 some MyProtocol,里面既有 MyC 类型,又有 MyC1,对于编译器来说是有冲突的,因此编译会报错。
实际使用中如何选择
其实上边已经讲解了两个关键字的区别,在实际使用过程中应该按照需要使用,不过应该遵守以下原则:
-
尽量使用
some,而不是any。 -
用
any关键字时记得考虑带来的性能影响。
本文同步自微信公众号 “iOS新知”,每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!