Introduction 该 keyword was introduced in Swift 5.1, while 现在,这些关键字可以在函数的参数中和类型定义之前使用,如下: some any protocol P {} struct S: P {} // 'any P' is an explicit existential type. let p: any P = S() // 'some P' is an opaque type. let p: some P = S() func f(_ p: any P) {} func f(_ p: some P) {} 在Swift 5中,它 关键字可以用来明确表示存在类型. 从 Swift 6 开始,存在类型必须用存在类型进行明确的拼写。 关键字 any any An opaque type helps describe the expected return type without defining a specific concrete type. This way, the compiler can get access to the actual type information and can potentially perform optimizations in this context. 存在类型与使用具体类型相比,成本显著高。存在类型可以存储符合协议的任何值,并且该值的类型可以动态变化,需要动态内存分配。 了解某些关键字 一个不透明的结果类型是由实现满足的暗示的通用位置持有人,所以你可以考虑这个: some protocol P {} struct S1 : P {} func f() -> some P { return S1() } 这里的关键是产生一种类型的函数。 具体返回一个单一、具体类型的值,附加到 如果函数试图返回各种匹配类型,则会导致编译器错误,因为多种类型无法满足默认的通用位置。 P P struct F1: P {} struct F2: P {} // error: Function declares an opaque return type, but the return // statements in its body do not have matching underlying types. func f(_ value: Bool) -> some P { if value { return F1() } else { return F2() } } 让我们来考虑不透明类型提供的优点,而不是协议返回类型。 Opaque result types can be used with PATs. The main limitation of using protocols is that protocols with associated types cannot be used as actual types. This means that the following code doesn’t compile: func collection() -> Collection { return ["1", "2", "3"] } As for opaque types, they are merely generic placeholders that can be used in such scenarios: // protocol Collection<Element> : Sequence func collection() -> some Collection { return ["1", "2", "3"] } Opaque result types have identity. Because opaque types guarantee that only one type will be returned, the compiler knows that a function must return the same type on several calls: func method() -> some Equatable { return "method" } let x = method() let y = method() print(x == y) // true Opaque result types compose with generic placeholders. Contrary to conventional protocol-typed values, opaque result types integrate effectively with standard generic placeholders. For instance: protocol P { var message: String { get } } struct M: P { var message: String } func makeM() -> some P { return M(message: "message") } func bar<T: P, U: P>(_ p1: T, _ p2: U) -> Bool { return p1.message == p2.message } let m1 = makeM() let m2 = makeM() print(bar(m1, m2)) However, it doesn’t work if make returns different types based on protocol . M() P protocol P { var message: String { get } } struct M: P { var message: String } struct T: P { var message: String } // error: function declares an opaque return type 'some P', but the return statements in its body do not have matching underlying types func makeM() -> some P { if .random() { return M(message: "M message") } else { return T(message: "T message") } } 理解任何关键字 让我们来看看下面的例子吧,假设我们有一个 议定书和本议定书的两个具体实施。 Drawable protocol Drawable { func draw() } struct Line: Drawable { let x1: Int let y1: Int let x2: Int let y2: Int func draw() { print("Draw Line") } } struct Point: Drawable { let x: Int let y: Int func draw() { print("Point") } } 我们有两个结构对象, 和 ,相应地. 让我们创建一个变量并存储一个 对象: Line Point Drawable var p1: any Drawable = Line(x1: 0, y1: 0, x2: 5, y2: 5) // 'any Drawable' is an explicit existential type p1.draw() // print "Draw Line" p1 = Point(x: 0, y: 0) p1.draw() // print "Point" We can switch between different implementations during runtime. Let’s consider another example: let array: [any Drawable] = [ Line(x1: 0, y1: 0, x2: 5, y2: 5), Line(x1: 0, y1: 0, x2: 5, y2: 5), Point(x: 0, y: 0) ] As we’re aware, achieving random access to any element within an array in constant time is feasible due to each element having the same memory size. In our example, where we store elements of varying sizes, you might wonder how we can retrieve an element from the array in such a scenario. print(MemoryLayout<Line>.size) // 32 print(MemoryLayout<Point>.size) // 16 It’s possible because the means indicates that we work with existential containers. The existential container encapsulates five machine words, allocating three for storing an object or a pointer to the object, one for a pointer to the virtual table, and another for a pointer to the witness table. any 存在性容器需要5个机器单词(在x64位系统中,5 * 8 = 40): Value buffer is a space for the instance VWT is a pointer to Value Witness Table PWT is a pointer to the Protocol Witness Table 每当代码中使用协议的值时,编译器会生成一个框,我们称之为存在性容器. 一个值的一个框. 由于它们可以存储任何符合协议类型的值,并且存储值的类型可以动态地改变,存在性容器需要动态内存分配,如果值不够小,以适应3个机器单词的缓冲器。 除了堆积分配和参考计数外,每个使用存在性容器都涉及指针间接和动态发送,无法优化。 使用三个单词来插入一个值,如果它匹配3个机器单词,或者,如果值超过3个机器单词,创建一个ARC管理的框,然后将该值复制到框中,并将指向ARC管理的框复制到容器的第一个单词中。 协议见证表是一个数组,每个协议都包含一个静态关联到存在类型的条目。 , there will be two entries, one for each protocol. As long as we have the Protocol Witness Table (PWT) that describes the type’s adherence to the protocol, we can create an existential container, pass it around, and use it for dynamic dispatch. P & Q 因此,我们有一个存在性容器,我们有一个协议见证表. 然而,我们不知道该值应该存储在存在性容器中的位置 - 它是否直接存储在容器中,或者如果我们只有对堆中的值的参考。 , , , 每个类型都有这样的表,它承担创建类型的实例的责任,决定该值应该存储在何处 - 在堆栈中或堆栈中等。 allocate copy destruct deallocate Now, we have a fixed-size container, which solves the issue of passing parameters of heterogeneous sizes, and a way to move this container. The remaining question is, what do we do with it? 至少,我们希望能够按协议规定的方式调用容器中存储值的必要成员(初始化器,属性 - 无论是存储还是计算,函数和订阅)。 但是,每个相应类型可以以不同的方式实现这些成员,例如,有些人可能会通过直接定义方法的要求,而另一个,比如一个类,可能会通过继承方法从一个超级类来满足它。 处理所有这些多样性是协议见证表(PWT)的主要目的,每个协议的合规性都有一个相同的表。 虽然这些函数可能位于不同的位置,但PWT的位置对于它相应的协议是独一无二的,因此,调用者可以检查容器的PWT,使用它来定位该协议的方法的实施,并调用它们。 重要的是要知道存在性是相对昂贵的使用,因为编译器和运行时间不能预先确定应该分配多少内存的具体对象,将填补存在性。 It’s important to know that existentials are relatively expensive to use because the compiler and runtime can’t pre-determine how much memory should be allocated for the concrete object that will fill in the existential. 摘要 We considered the differences between 和 keywords. On one hand, it significantly enhanced the syntax and readability of our generic code; on the other hand, it introduced new avenues for us to craft generic code in a more efficient manner. some any Thanks for reading.