Bonjour à tous! Nous partageons avec vous une traduction préparée spécialement pour les étudiants du cours
«Développeur iOS. Cours avancé .
" Bonne lecture.
Fonction générique, type générique et restrictions de typeQue sont les génériques?
Quand ils travaillent, vous les aimez et quand non, vous les détestez!Dans la vraie vie, tout le monde connaßt le pouvoir des génériques: se réveiller le matin, décider quoi boire, remplir une tasse.
ïž
Swift est un langage à caractÚres sûrs. Chaque fois que nous travaillons avec des types, nous devons les spécifier explicitement. Par exemple, nous avons besoin d'une fonction qui fonctionnera avec plusieurs types. Swift a les types
Any
et
AnyObject
, mais ils doivent ĂȘtre utilisĂ©s avec
AnyObject
et pas toujours. L'utilisation de
Any
et
AnyObject
rendra votre code peu fiable, car il sera impossible de suivre la non-concordance de type pendant la compilation. C'est là que les génériques viennent à la rescousse.
Le code générique vous permet de créer des fonctions et des types de données réutilisables qui peuvent fonctionner avec tout type répondant à certaines restrictions, tout en garantissant la sécurité des types lors de la compilation. Cette approche vous permet d'écrire du code qui aide à éviter la duplication et exprime sa fonctionnalité de maniÚre claire et abstraite. Par exemple, des types tels que
Array
,
Set
et
Dictionary
utilisent des génériques pour stocker des éléments.
Disons que nous devons créer un tableau composé de valeurs entiÚres et de chaßnes. Pour résoudre ce problÚme, je vais créer deux fonctions.
let intArray = [1, 2, 3, 4] let stringArray = [a, b, c, d] func printInts(array: [Int]) { print(intArray.map { $0 }) } func printStrings(array: [String]) { print(stringArray.map { $0 }) }
Maintenant, je dois sortir un tableau d'éléments de type float ou un tableau d'objets utilisateur. Si nous regardons les fonctions ci-dessus, nous verrons que seule la différence de type est utilisée. Par conséquent, au lieu de dupliquer le code, nous pouvons écrire une fonction générique à réutiliser.
L'histoire des génériques dans Swift

Fonctions génériques
La fonction générique peut fonctionner avec n'importe quel paramÚtre universel de type
T
Le nom du type ne dit rien sur ce que devrait ĂȘtre
, mais il indique que les deux tableaux doivent ĂȘtre de type
, quel que soit
Le type lui-mĂȘme Ă utiliser au lieu de
est déterminé à chaque appel de la fonction
print(
_:
)
.
func print<T>(array: [T]) { print(array.map { $0 }) }
Types génériques ou polymorphisme paramétrique
Le type générique T de l'exemple ci-dessus est un paramÚtre de type. Vous pouvez spécifier plusieurs paramÚtres de type en écrivant plusieurs noms de paramÚtres de type entre crochets, séparés par des virgules.
Si vous regardez Array and Dictionary <Key, Element>, vous remarquerez qu'ils ont des paramÚtres de type nommés, c'est-à -dire Element et Key, Element, qui parlent de la relation entre le paramÚtre de type et le type ou la fonction générique dans lequel il est utilisé. .
Remarque: Donnez toujours des noms aux paramĂštres de type dans une notation CamelCase (par exemple,
T
et
TypeParameter
) pour montrer qu'ils sont un nom pour le type, pas une valeur.
Types génériques
Ce sont des classes, des structures et des énumérations personnalisées qui peuvent fonctionner avec n'importe quel type, comme les tableaux et les dictionnaires.
Créons une pile
import Foundation enum StackError: Error { case Empty(message: String) } public struct Stack { var array: [Int] = [] init(capacity: Int) { array.reserveCapacity(capacity) } public mutating func push(element: Int) { array.append(element) } public mutating func pop() -> Int? { return array.popLast() } public func peek() throws -> Int { guard !isEmpty(), let lastElement = array.last else { throw StackError.Empty(message: "Array is empty") } return lastElement } func isEmpty() -> Bool { return array.isEmpty } } extension Stack: CustomStringConvertible { public var description: String { let elements = array.map{ "\($0)" }.joined(separator: "\n") return elements } } var stack = Stack(capacity: 10) stack.push(element: 1) stack.push(element: 2) print(stack) stack.pop() stack.pop() stack.push(element: 5) stack.push(element: 3) stack.push(element: 4) print(stack)
Maintenant, cette pile est capable d'accepter uniquement des éléments entiers, et si j'ai besoin de stocker des éléments d'un type différent, je devrai soit créer une autre pile, soit la convertir en une apparence générique.
enum StackError: Error { case Empty(message: String) } public struct Stack<T> { var array: [T] = [] init(capacity: Int) { array.reserveCapacity(capacity) } public mutating func push(element: T) { array.append(element) } public mutating func pop() -> T? { return array.popLast() } public func peek() throws -> T { guard !isEmpty(), let lastElement = array.last else { throw StackError.Empty(message: "Array is empty") } return lastElement } func isEmpty() -> Bool { return array.isEmpty } } extension Stack: CustomStringConvertible { public var description: String { let elements = array.map{ "\($0)" }.joined(separator: "\n") return elements } } var stack = Stack<Int>(capacity: 10) stack.push(element: 1) stack.push(element: 2) print(stack) var strigStack = Stack<String>(capacity: 10) strigStack.push(element: "aaina") print(strigStack)
Limitations des types génériques
Puisqu'un gĂ©nĂ©rique peut ĂȘtre de tout type, vous ne pouvez pas en faire grand-chose. Il est parfois utile d'appliquer des contraintes aux types pouvant ĂȘtre utilisĂ©s avec des fonctions gĂ©nĂ©riques ou des types gĂ©nĂ©riques. Les restrictions de type indiquent que le paramĂštre de type doit correspondre Ă un protocole ou une composition de protocole spĂ©cifique.
Par exemple, le type Swift
Dictionary
impose des restrictions sur les types qui peuvent ĂȘtre utilisĂ©s comme clĂ©s pour un dictionnaire. Le dictionnaire nĂ©cessite que les clĂ©s soient hachĂ©es afin de pouvoir vĂ©rifier s'il contient dĂ©jĂ des valeurs pour une clĂ© particuliĂšre.
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
Essentiellement, nous avons créé une pile de type T, mais nous ne pouvons pas comparer deux piles, car ici les types ne correspondent pas Ă
Equatable
. Nous devons changer cela pour utiliser
Stack<
T:
Equatable
>
.
Comment fonctionnent les génériques? Regardons un exemple.
func min<T: Comparable>(_ x: T, _ y: T) -> T { return y < x ? y : x }
Le compilateur manque de deux choses nécessaires pour créer le code de fonction:
- Tailles des variables de type T;
- Les adresses de la surcharge spĂ©cifique de la fonction <, qui doit ĂȘtre appelĂ©e au moment de l'exĂ©cution.
Chaque fois que le compilateur rencontre une valeur de type gĂ©nĂ©rique, il place la valeur dans un conteneur. Ce conteneur a une taille fixe pour stocker des valeurs. Dans le cas oĂč la valeur est trop grande, Swift l'alloue sur le tas et stocke un lien vers celle-ci dans le conteneur.
Le compilateur conserve également une liste d'une ou plusieurs tables témoins pour chaque paramÚtre générique: une table témoin pour les valeurs, plus une table témoin pour chaque type de protocole de restriction. Les tables témoins sont utilisées pour envoyer dynamiquement des appels de fonction aux implémentations souhaitées lors de l'exécution.
La fin de la premiĂšre partie. Par tradition, nous attendons vos commentaires, amis.
DeuxiĂšme partie