Introduction Le Le mot clé a été introduit dans Swift 5.1, alors que Le mot clé a été introduit dans Swift 5.6.Maintenant, ces mots clés peuvent être utilisés dans les paramètres de la fonction et avant la définition d'un type, comme suit: 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) {} Dans Swift 5, le Le mot clé peut être utilisé pour désigner explicitement un type existentiel. À partir de Swift 6, les types existentiels doivent être explicitement écrite avec le Mots clés . any any Un type opaque permet de décrire le type de retour attendu sans définir un type de béton spécifique.De cette façon, le compilateur peut accéder aux informations de type réelles et peut potentiellement effectuer des optimisations dans ce contexte. Un type existentiel peut stocker n'importe quelle valeur qui correspond au protocole, et le type de la valeur peut changer de manière dynamique, nécessitant une allocation de mémoire dynamique. Comprendre le mot clé Un type de résultat opaque est un placeholder générique implicite satisfait par la mise en œuvre, de sorte que vous pouvez penser à ceci: some protocol P {} struct S1 : P {} func f() -> some P { return S1() } La clé ici est qu'une fonction qui produit un type renvoie spécifiquement une valeur d'un type concret singulier adhérant à Si la fonction tente de retourner différents types de conformation, cela entraînera une erreur de compilateur, car le place-tenant générique implicite ne peut pas être satisfait par plusieurs types. 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() } } Considérons les avantages que les types opaques offrent par rapport aux types de retour de protocole. 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") } } Comprendre n'importe quel mot clé Prenons l’exemple suivant : supposons que nous ayons un du protocole et deux mesures concrètes de ce protocole. 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") } } Nous avons deux objets structurés, et Créer une variable et enregistrer une Objet : 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" Nous pouvons échanger entre différentes implémentations pendant le temps d'exécution. 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) ] Comme nous le savons, il est possible d’obtenir un accès aléatoire à n’importe quel élément dans une matrice en temps constant car chaque élément a la même taille de mémoire. Dans notre exemple, où nous stockons des éléments de tailles différentes, vous pourriez vous demander comment nous pouvons récupérer un élément de la matrice dans un tel scénario. print(MemoryLayout<Line>.size) // 32 print(MemoryLayout<Point>.size) // 16 C’est possible parce que le Le conteneur existentiel encapsule cinq mots de machine, allouant trois pour stocker un objet ou un pointeur à l'objet, un pour un pointeur à la table virtuelle, et un autre pour un pointeur à la table des témoins. any Le conteneur existentiel prend 5 mots de machine (dans un système x64-bit, 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 Chaque fois qu'une valeur du protocole est utilisée dans le code, le compilateur génère une boîte que nous appelons un conteneur existentiel. Une boîte pour une valeur. Puisqu'ils peuvent stocker n'importe quelle valeur dont le type correspond au protocole, et le type de la valeur stockée peut changer de manière dynamique, le conteneur existentiel nécessite une allocation de mémoire dynamique si la valeur n'est pas suffisamment petite pour s'adapter à un tampon de 3 mots de machine. En plus de l'allocation de pile et du comptage de référence, chaque utilisation d'un conteneur existentiel implique une indirection de pointeur et une expédition dynamique, qui ne peuvent pas être optimisées. Trois mots sont utilisés soit pour insérer une valeur si elle correspond à 3 mots de machine, soit, si la valeur est supérieure à 3 mots de machine, une case gérée par ARC est créée. La valeur est ensuite copiée dans la case, et un pointeur vers la case gérée par ARC est copié dans le premier mot du conteneur. Les deux mots restants ne sont pas utilisés. Les mots suivants sont utilisés pour indiquer respectivement la Table des témoins de valeur (VWT) et la Table des témoins de protocole (PWT). La Table des témoins de protocole est une matrice qui contient une entrée pour chaque protocole statiquement associé au type existentiel. , il y aura deux entrées, une pour chaque protocole. Tant que nous avons la Table des témoins du protocole (PWT) qui décrit l'adhésion du type au protocole, nous pouvons créer un conteneur existentiel, le passer et l'utiliser pour l'expédition dynamique. P & Q Donc, nous avons un conteneur existentiel, et nous avons un tableau témoin de protocole. Cependant, nous ne savons pas où cette valeur devrait être stockée dans le conteneur existentiel – qu’elle soit stockée directement dans le conteneur ou si nous n’avons qu’une référence à la valeur dans le tas. Pour répondre à ces questions, nous nous tournons vers une table auxiliaire appelée Tableau témoin de valeur (VWT). , à , à , à Chaque type a un tel tableau, et il assume la responsabilité de créer une instance du type, de décider où la valeur doit être stockée - dans la pile ou dans le pile, etc. allocate copy destruct deallocate Maintenant, nous avons un conteneur de taille fixe, qui résout le problème du passage des paramètres de tailles hétérogènes, et un moyen de déplacer ce conteneur. Eh bien, au moins, nous voulons pouvoir invoquer les membres nécessaires de la valeur stockée dans le conteneur, comme spécifié dans le protocole (initialisateurs, propriétés - stockées ou calculées, fonctions et abonnements). Cependant, chaque type correspondant peut mettre en œuvre ces membres différemment. Par exemple, certains peuvent satisfaire à la demande de méthode en la définissant directement, tandis qu'un autre, disons une classe, peut la satisfaire en héritant de la méthode d'une superclasse. Certains peuvent mettre en œuvre une propriété en tant que propriété stockée, d'autres en tant que propriété calculée, et certains rétroactivement (via une extension). Gérer toute cette diversité est le but principal de la Table des témoins de protocole (PWT). Il existe exactement une telle table pour chaque conformité de protocole. Il contient un ensemble de pointeurs de fonction qui indiquent la mise en œuvre des exigences du protocole. Bien que les fonctions puissent être situées à des endroits différents, l'emplacement du PWT est unique pour le protocole auquel il correspond.En conséquence, l'appelant peut inspecter le PWT du conteneur, l'utiliser pour localiser la mise en œuvre des méthodes du protocole et les invoquer. Il est important de savoir que les existentiels sont relativement coûteux à utiliser car le compilateur et le temps d’exécution ne peuvent pas pré-déterminer combien de mémoire doit être alloué à l’objet concret qui remplira l’existentiel. Il est important de savoir que les existentiels sont relativement coûteux à utiliser car le compilateur et le temps d’exécution ne peuvent pas pré-déterminer combien de mémoire doit être alloué à l’objet concret qui remplira l’existentiel. Résumé Nous avons examiné les différences entre et D’une part, il a considérablement amélioré la syntaxe et la lisibilité de notre code générique ; d’autre part, il a introduit de nouvelles voies pour que nous puissions fabriquer le code générique de manière plus efficace. some any Merci pour la lecture.