Einführung Die Keyword wurde in Swift 5.1 eingeführt, während Keyword wurde in Swift 5.6 eingeführt.Nun können diese Keywords in den Parametern der Funktion und vor der Definition eines Typs wie folgt verwendet werden: 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) {} In Swift 5, die Schlüsselwort kann verwendet werden, um explizit einen existentiellen Typ zu bezeichnen. Ab Swift 6 müssen existentielle Typen explizit mit dem existentiellen Typ geschrieben werden. Das Schlüsselwort. any any Ein unsichtbarer Typ hilft, den erwarteten Return-Typ zu beschreiben, ohne einen spezifischen Betontyp zu definieren. Auf diese Weise kann der Compiler auf die tatsächlichen Typinformationen zugreifen und in diesem Kontext möglicherweise Optimierungen vornehmen. Ein existentieller Typ kann jeden Wert speichern, der dem Protokoll entspricht, und der Typ des Wertes kann sich dynamisch ändern, was eine dynamische Speicherallokation erfordert. Einige Schlüsselwörter verstehen Ein opaques Ergebnis-Typ ist ein impliziter generischer Platzhalter, der durch die Implementierung zufrieden ist, so dass Sie dies denken können: some protocol P {} struct S1 : P {} func f() -> some P { return S1() } Der Schlüsselaufnahme hier ist, dass eine Funktion, die einen Typ spezifisch einen Wert eines einzelnen, konkreten Typs zurückgibt, der Wenn die Funktion versucht, verschiedene konformierende Typen zurückzugeben, führt dies zu einem Compilerfehler, da der implizite generische Platzhalter nicht durch mehrere Typen befriedigt werden kann. 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() } } Betrachten wir die Vorteile, die opache Typen gegenüber Protokoll-Return-Typen bieten. 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") } } Jedes Schlüsselwort verstehen Betrachten wir das folgende Beispiel: Nehmen wir an, wir haben eine Protokoll und zwei konkrete Umsetzungen dieses Protokolls. 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") } } Wir haben zwei strukturelle Objekte, und So erstellen wir eine Variable und speichern eine Das Objekt: 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" Wir können während der Laufzeit zwischen verschiedenen Implementierungen wechseln.Lassen Sie uns ein anderes Beispiel betrachten: 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) ] Wie wir wissen, ist es möglich, zufälligen Zugriff auf jedes Element innerhalb eines Array in konstanten Zeiten zu erzielen, da jedes Element die gleiche Speichergröße hat. print(MemoryLayout<Line>.size) // 32 print(MemoryLayout<Point>.size) // 16 Es ist möglich, weil die Der existentielle Behälter verankert fünf Maschinenwörter, wobei drei für die Speicherung eines Objekts oder eines Zeigers an das Objekt, ein für einen Zeiger an die virtuelle Tabelle und ein anderer für einen Zeiger an die Zeugentabelle zugewiesen werden. any Der existentielle Behälter nimmt 5 Maschinenwörter (in einem x64-Bit-System, 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 Jedes Mal, wenn ein Wert des Protokolls in Code verwendet wird, erzeugt der Compiler ein Feld, das wir einen existentiellen Behälter nennen. Ein Feld für einen Wert. Da sie jeden Wert speichern können, dessen Typ dem Protokoll entspricht, und der Typ des gespeicherten Wertes sich dynamisch ändern kann, erfordert der existentielle Behälter dynamische Speicherallokation, wenn der Wert nicht klein genug ist, um in einen Puffer von 3 Maschinenwörtern zu passen. Zusätzlich zu Stapelallokation und Referenzzählung beinhaltet jede Verwendung eines existentiellen Behälters Pointer indirektion und dynamischen Versand, der nicht optimiert werden kann. Drei Wörter werden entweder verwendet, um einen Wert einzuschneiden, wenn er mit 3 Maschinenwörtern übereinstimmt, oder, wenn der Wert mehr als 3 Maschinenwörter beträgt, wird ein ARC-geführtes Feld erstellt. Der Wert wird dann in das Feld kopiert, und ein Zeiger in das ARC-geführte Feld wird in das erste Wort des Behälters kopiert. Die verbleibenden zwei Wörter werden nicht verwendet. Die Protokollzeugentabelle ist ein Array, das einen Eintrag für jedes Protokoll enthält, der statisch mit dem existentiellen Typ assoziiert ist. Solange wir die Protocol Witness Table (PWT) haben, die die Übereinstimmung des Typs mit dem Protokoll beschreibt, können wir einen existentiellen Behälter erstellen, ihn umgehen und für dynamischen Versand verwenden. P & Q Wir wissen jedoch nicht, wo dieser Wert im existentiellen Behälter gespeichert werden sollte – ob er direkt im Behälter gespeichert ist oder wenn wir nur eine Bezugnahme auf den Wert im Stapel haben. , der , der , der Jeder Typ hat eine solche Tabelle, und er übernimmt die Verantwortung, eine Instanz des Typs zu erstellen, zu entscheiden, wo der Wert gespeichert werden soll - im Stapel oder im Stapel usw. allocate copy destruct deallocate Jetzt haben wir einen festen Container, der das Problem der Übertragung von Parametern heterogener Größen löst, und eine Möglichkeit, diesen Container zu bewegen. Nun, zumindest wollen wir in der Lage sein, die notwendigen Mitglieder des gespeicherten Wertes im Container, wie im Protokoll angegeben, anzurufen (Initialisierer, Eigenschaften - ob gespeichert oder berechnet, Funktionen und Abonnements). Jedoch kann jeder entsprechende Typ diese Mitglieder unterschiedlich implementieren. Zum Beispiel können einige die Methodenanforderung durch direkte Definition befriedigen, während ein anderer, sagen wir eine Klasse, sie durch Vererbung der Methode von einer Superklasse befriedigen kann. Die Handhabung all dieser Vielfalt ist der primäre Zweck der Protokollzeugentabelle (PWT). Es gibt genau eine solche Tabelle für jede Protokollkonformität. Während sich die Funktionen an verschiedenen Orten befinden können, ist der Standort des PWT einzigartig für das Protokoll, dem es entspricht.Daher kann der Anrufer das PWT des Behälters inspizieren, es verwenden, um die Implementierung der Protokollmethoden zu lokalisieren und sie anzurufen. Es ist wichtig zu wissen, dass Existentiale relativ teuer zu verwenden sind, weil der Compiler und die Laufzeit nicht vorab bestimmen können, wie viel Speicher für das konkrete Objekt, das das Existentielle füllen wird, zugewiesen werden sollte. Es ist wichtig zu wissen, dass Existentiale relativ teuer zu verwenden sind, weil der Compiler und die Laufzeit nicht vorab bestimmen können, wie viel Speicher für das konkrete Objekt, das das Existentielle füllen wird, zugewiesen werden sollte. Zusammenfassung We considered the differences between und Auf der einen Seite hat es die Syntax und Lesbarkeit unseres generischen Codes signifikant verbessert; auf der anderen Seite haben wir neue Wege eingeführt, um generischen Code effizienter zu gestalten. some any Danke fürs Lesen.