Introduktion den nyckelord introducerades i Swift 5.1, medan Nyckelordet introducerades i Swift 5.6.Nu kan dessa nyckelord användas i funktionens parametrar och före definitionen av en typ, enligt följande: 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) {} I Swift 5, den Nyckelord kan användas för att uttryckligen beteckna en existentiell typ. Från och med Swift 6 krävs att existentiella typer uttryckligen skrivs med Ett nyckelord. any any En opac typ hjälper till att beskriva den förväntade returtypen utan att definiera en specifik betongtyp. På så sätt kan kompileraren få tillgång till den faktiska typinformationen och kan potentiellt utföra optimeringar i detta sammanhang. En existentiell typ kan lagra något värde som överensstämmer med protokollet, och typen av värdet kan ändras dynamiskt, vilket kräver dynamisk minneallokering.Kod som använder existentiella typer uppstår pekare indirektion och dynamisk metod dispatch som inte kan optimeras. Förstå några nyckelord En opac resultatyp är en implicit generisk platshållare som är tillfredsställd av implementeringen, så du kan tänka på detta: some protocol P {} struct S1 : P {} func f() -> some P { return S1() } Nyckeln här är att en funktion som producerar en typ av specifikt returnerar ett värde av en singular, konkret typ som följer Om funktionen försöker returnera olika konformationstyper kommer det att resultera i ett kompilerfel, eftersom den implicita generiska platshållaren inte kan tillfredsställas av flera typer. 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() } } Låt oss överväga de fördelar som ogenomskinliga typer erbjuder över protokollåtervändningstyper. 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") } } Förstå alla nyckelord Låt oss titta på följande exempel.Anta att vi har en protokoll och två konkreta genomföranden av detta protokoll. 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") } } Vi har två strukturobjekt, och Låt oss skapa en variabel och lagra en och 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" Vi kan växla mellan olika implementeringar under körtiden. Låt oss överväga ett annat exempel: 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) ] Som vi vet är det möjligt att få slumpmässig åtkomst till något element i en matris i konstant tid eftersom varje element har samma minnesstorlek.I vårt exempel, där vi lagrar element av olika storlekar, kanske du undrar hur vi kan hämta ett element från matriset i ett sådant scenario. print(MemoryLayout<Line>.size) // 32 print(MemoryLayout<Point>.size) // 16 Det är möjligt eftersom den Den existentiella behållaren inkapslar fem maskinord, som tilldelar tre för att lagra ett objekt eller en pekare till objektet, en för en pekare till det virtuella bordet och en annan för en pekare till vittnetabellen. any Den existentiella behållaren tar 5 maskinord (i ett x64-bitarsystem, 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 Varje gång ett värde av protokollet används i koden genererar kompileraren en ruta som vi kallar en existentiell behållare.En ruta för ett värde.Eftersom de kan lagra något värde vars typ överensstämmer med protokollet, och typen av det lagrade värdet kan ändras dynamiskt, kräver den existentiella behållaren dynamisk minneallokering om värdet inte är tillräckligt litet för att passa in i en buffert med 3 maskinord. Förutom stapelallokering och referensräkning innebär varje användning av en existentiell behållare pekare indirektion och dynamisk dispatch, som inte kan optimeras. Tre ord används antingen för att infoga ett värde om det passar 3 maskinord, eller om värdet är mer än 3 maskinord skapas en ARC-hanterad ruta. Värdet kopieras sedan till rutan och en pekare till den ARC-hanterade rutan kopieras till det första ordet i behållaren. De återstående två orden används inte. Följande ord används för att peka på värdevittnestabellen (VWT) och protokollvittnestabellen (PWT), respektive. Protocol Witness Table är en matris som innehåller en post för varje protokoll som är statiskt associerad med den existentiella typen. Så länge vi har protokollvittnestabellen (PWT) som beskriver typens överensstämmelse med protokollet, kan vi skapa en existentiell behållare, passera den runt och använda den för dynamisk dispatch. P & Q Så vi har en existentiell behållare, och vi har en protokollvittnestabell. Men vi vet inte var detta värde ska lagras i den existentiella behållaren – om det lagras direkt i behållaren eller om vi bara har en referens till värdet i högen. och och och Varje typ har en sådan tabell, och den tar på sig ansvaret för att skapa en instans av typen, bestämma var värdet ska lagras - i stacken eller i högen, etc. allocate copy destruct deallocate Nu har vi en fast storlek behållare, som löser problemet med att passera parametrar av heterogena storlekar, och ett sätt att flytta denna behållare. Tja, åtminstone vill vi kunna åberopa de nödvändiga medlemmarna av det lagrade värdet i behållaren, som anges i protokollet (initializers, egenskaper - vare sig lagrade eller beräknade, funktioner och prenumerationer). Men varje motsvarande typ kan implementera dessa medlemmar på olika sätt. Till exempel kan vissa tillfredsställa metodkravet genom att direkt definiera det, medan en annan, säg en klass, kan tillfredsställa det genom att ärva metoden från en superklass. Att hantera all denna mångfald är det primära syftet med protokollvittnestabellen (PWT). Det finns exakt en sådan tabell för varje protokollkonformitet. Även om funktionerna kan vara placerade på olika platser är PWT:s plats unik för det protokoll som den motsvarar.Som ett resultat kan uppringaren inspektera behållarens PWT, använda den för att lokalisera genomförandet av protokollets metoder och åberopa dem. Det är viktigt att veta att existentialer är relativt dyra att använda eftersom kompileraren och körtiden inte kan förutbestämma hur mycket minne som ska tilldelas för det konkreta objektet som kommer att fylla det existentiella. Det är viktigt att veta att existentialer är relativt dyra att använda eftersom kompileraren och körtiden inte kan förutbestämma hur mycket minne som ska tilldelas för det konkreta objektet som kommer att fylla det existentiella. Sammanfattning Vi har tittat på skillnaderna mellan och Å ena sidan förbättrade det signifikant syntaxen och läsbarheten av vår generiska kod; å andra sidan introducerade det nya vägar för oss att skapa generisk kod på ett mer effektivt sätt. some any Tack för läsningen.