Génériques Swift

Swift fournit des génériques pour que vous puissiez écrire des fonctions et des types flexibles et réutilisables.

La bibliothèque standard Swift est construite avec du code générique.

Les types Array et Dictionary de Swift sont des collections génériques.

Vous pouvez créer un tableau d'Int, un tableau de String, ou même un tableau de données de tout autre type Swift.

L'exemple suivant est une fonction d'échange non générique utilisée pour échanger deux valeurs Int :

Exemple

// Définir une fonction pour échanger deux variables func swapTwoInts(_ a: inout Int, _ b: inout Int) { let temporaryA = a a = b b = temporaryA } var numb1 = 100 var numb2 = 200 print("Avant l'échange :\(numb1) et\(numb2)") swapTwoInts(&numb1, &numb2) print("Après l'échange :\(numb1) et\(numb2)")

La sortie d'exécution du programme ci-dessus est :

交换前数据: 100 和 200
交换后数据: 200 和 100

L'exemple ci-dessus ne s'applique qu'à l'échange de variables de type entier Int. Si vous voulez échanger deux valeurs String ou Double, vous devez écrire à nouveau une fonction correspondante, telle que swapTwoStrings(_:_:) et swapTwoDoubles(_:_:), comme montré ci-dessous :

Fonctions d'échange de valeurs String et Double

func swapTwoStrings(_ a: inout String, _ b: inout String) { let temporaryA = a a = b b = temporaryA } func swapTwoDoubles(_ a: inout Double, _ b: inout Double) { let temporaryA = a a = b b = temporaryA }

D'après le code ci-dessus, leur code fonctionnel est le même, seuls les types sont différents. Dans ce cas, nous pouvons utiliser des génériques pour éviter d'écrire du code répétitif.

Les génériques utilisent un nom de type de substitution (représenté ici par la lettre T) pour remplacer le nom de type réel (tel que Int, String ou Double).

func swapTwoValues<T>(_ a: inout T, _ b: inout T)

swapTwoValues est suivi du nom de type de substitution (T), placé entre chevrons (<T>). Ces chevrons indiquent à Swift que T est un nom de type de substitution dans la définition de la fonction swapTwoValues(_:_:), ainsi Swift ne cherchera pas un type réel nommé T.

L'exemple suivant est une fonction générique exchange utilisée pour échanger deux valeurs Int et String :

Exemple

// Définir une fonction pour échanger deux variables func swapTwoValues<T>(_ a: inout T, _ b: inout T) { let temporaryA = a a = b b = temporaryA } var numb1 = 100 var numb2 = 200 print("Avant l'échange :\(numb1) et\(numb2)") swapTwoValues(&numb1, &numb2) print("Après l'échange :\(numb1) et\(numb2)") var str1 = "A" var str2 = "B" print("Avant l'échange :\(str1) et\(str2)") swapTwoValues(&str1, &str2) print("Après l'échange :\(str1) et\(str2)")

La sortie d'exécution du programme ci-dessus est :

交换前数据:  100 和 200
交换后数据: 200 和 100
交换前数据:  A 和 B
交换后数据: B 和 A

Types génériques

Swift vous permet de définir vos propres types génériques.

Les classes, structures et énumérations personnalisées fonctionnent avec n'importe quel type, tout comme l'utilisation de Array et Dictionary.

Ensuite, nous allons écrire un type de collection générique nommé Stack (pile). Une pile permet uniquement d'ajouter de nouveaux éléments à la fin de la collection (opération appelée empilement), et les éléments ne peuvent être retirés que de la fin également (opération appelée dépilement).

L'image est expliquée de gauche à droite comme suit :

  • Trois valeurs se trouvent dans la pile.
  • Une quatrième valeur est empilée au sommet de la pile.
  • Maintenant, il y a quatre valeurs dans la pile, avec la valeur la plus récemment empilée au sommet.
  • La valeur au sommet de la pile est retirée, ou dépilée.
  • Après avoir retiré une valeur, la pile ne contient à nouveau plus que trois valeurs.

L'exemple suivant est une version non générique de la pile, en prenant une pile d'Int comme exemple :

Pile d'Int

struct IntStack { var items = [Int]() mutating func push(_ item: Int) { items.append(item) } mutating func pop() -> Int { return items.removeLast() } }

Cette structure utilise une propriété Array nommée items dans la pile pour stocker des valeurs. Stack fournit deux méthodes : push(_:) et pop(), utilisées pour pousser des valeurs dans la pile et retirer des valeurs de la pile. Ces méthodes sont marquées comme mutating car elles doivent modifier le tableau items de la structure.

La structure IntStack ci-dessus ne peut être utilisée que pour le type Int. Cependant, vous pouvez définir une structure Stack générique afin qu'elle puisse gérer des valeurs de n'importe quel type.

Ci-dessous se trouve la version générique du même code :

Stack générique

struct Stack<Element> { var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } } var stackOfStrings = Stack<String>() print("Élément String poussé sur la pile :") stackOfStrings.push("google") stackOfStrings.push("exemple") print(stackOfStrings.items); let deletetos = stackOfStrings.pop() print("Élément dépilé :" + deletetos) var stackOfInts = Stack<Int>() print("Élément entier empilé sur la pile :") stackOfInts.push(1) stackOfInts.push(2) print(stackOfInts.items);

Le résultat d'exécution de l'instance est :

字符串元素入栈: 
["google", "example"]
出栈元素: example
整数元素入栈: 
[1, 2]

Stack est essentiellement identique à IntStack, avec le paramètre de type de remplacement Element qui remplace le type Int réel.

Dans l'exemple ci-dessus, Element est utilisé comme espace réservé aux trois emplacements suivants :

  • Création de la propriétéitems, en utilisant un tableau vide du typeElementpour l'initialiser.
  • Spécifiez pourpush(_:)que le seul paramètre de la méthodeitemdoit être de typeElementtype.
  • Spécifiez pourpop()que le type de valeur de retour de la méthode doit être de typeElement.

Extension d’un type générique

Lorsque vous étendez un type générique (à l’aide du mot-clé extension), vous n’avez pas besoin de fournir une liste de paramètres de type dans la définition de l’extension. Plus commodément, la liste des paramètres de type déclarée dans la définition du type d’origine est disponible dans l’extension, et les noms des paramètres du type d’origine sont utilisés comme références aux paramètres de type dans la définition d’origine.

L’exemple suivant étend le type générique Stack, en ajoutant une propriété calculée en lecture seule nommée topItem, qui renvoie l’élément au sommet de la pile actuelle sans le retirer de la pile :

Génériques

struct Stack<Element> { var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } } extension Stack { var topItem: Element? { return items.isEmpty ? nil : items[items.count - 1] } } var stackOfStrings = Stack<String>() print("Élément String poussé sur la pile :") stackOfStrings.push("google") stackOfStrings.push("exemple") if let topItem = stackOfStrings.topItem { print("L’élément au sommet de la pile est :\(topItem).") } print(stackOfStrings.items)

Dans l'exemple, la propriété topItem renvoie une valeur optionnelle de type Element. Lorsque la pile est vide, topItem renvoie nil ; lorsqu'elle n'est pas vide, topItem renvoie le dernier élément du tableau items.

La sortie d'exécution du programme ci-dessus est :

字符串元素入栈: 
栈中的顶部元素是:example.
["google", "example"]

Nous pouvons également spécifier un type associé en étendant un type existant.

Par exemple, le type Array de Swift fournit déjà une méthode append(_:), une propriété count et un indice qui accepte une valeur d'index Int pour récupérer ses éléments. Ces trois fonctionnalités répondent toutes aux exigences du protocole Container, donc vous pouvez étendre Array simplement en déclarant qu'Array adopte le protocole.

L'exemple suivant crée uniquement une extension vide :

extension Array: Container {}

Contraintes de type

Les contraintes de type spécifient qu'un paramètre de type doit hériter d'une classe spécifiée, ou se conformer à un protocole particulier ou à une composition de protocoles.

Syntaxe des contraintes de type

Vous pouvez écrire une contrainte de type après le nom d'un paramètre de type, séparée par deux-points, dans le cadre de la chaîne de paramètres de type. La syntaxe de base de ces contraintes de type sur les fonctions génériques est présentée ci-dessous (identique à la syntaxe des types génériques) :

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // 这里是泛型函数的函数体部分
}

Cette fonction a deux paramètres de type. Le premier paramètre de type, T, a une contrainte de type exigeant que T soit une sous-classe de SomeClass ; le second paramètre de type, U, a une contrainte de type exigeant que U se conforme au protocole SomeProtocol.

Exemple

Génériques

// Fonction non générique, trouver l'index d'une chaîne spécifiée dans un tableau func findIndex(ofString valueToFind: String, in array: [String]) -> Int? { for (index, value) in array.enumerated() { if value == valueToFind { // Renvoie la valeur d'index si trouvée return index } } return nil } let strings = ["google", "weibo", "taobao", "example", "facebook"] if let foundIndex = findIndex(ofString: "example", in: strings) { print("L'indice de example est\(foundIndex)") }

L'indice commence à 0.

La sortie d'exécution du programme ci-dessus est :

example 的索引为 3

Types associés

En Swift, le mot-clé associatedtype est utilisé pour définir des instances de type associé.

L’exemple suivant définit un protocole Container, qui définit un type associé ItemType.

Le protocole Container ne spécifie que trois fonctionnalités que tout type conforme au protocole Container doit fournir. Un type qui se conforme au protocole peut également fournir des fonctionnalités supplémentaires tout en satisfaisant ces trois conditions.

// Container 协议
protocol Container {
    associatedtype ItemType
    // 添加一个新元素到容器里
    mutating func append(_ item: ItemType)
    // 获取容器中元素的数
    var count: Int { get }
    // 通过索引值类型为 Int 的下标检索到容器中的每一个元素
    subscript(i: Int) -> ItemType { get }
}

// Stack 结构体遵从 Container 协议
struct Stack<Element>: Container {
    // Stack<Element> 的原始实现部分
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // Container 协议的实现部分
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

var tos = Stack<String>()
tos.push("google")
tos.push("example")
tos.push("taobao")
// 元素列表
print(tos.items)
// 元素个数
print( tos.count)

La sortie d'exécution du programme ci-dessus est :

["google", "example", "taobao"]
3

Clauses Where

Les contraintes de type peuvent garantir qu’un type se conforme aux contraintes définies par une fonction ou une classe générique.

Vous pouvez définir des contraintes sur les paramètres dans la liste de paramètres à l’aide d’une clause where.

Vous pouvez écrire une instruction where immédiatement après la liste de paramètres de type, suivie d’une ou plusieurs contraintes sur des types associés et/ou d’une ou plusieurs relations d’égalité entre des types et des types associés.

Exemple

L’exemple suivant définit une fonction générique nommée allItemsMatch, utilisée pour vérifier si deux instances de Container contiennent les mêmes éléments dans le même ordre.

Si tous les éléments correspondent, elle renvoie true ; sinon, elle renvoie false.

Génériques

// Protocole Container protocol Container { associatedtype ItemType // Ajoute un nouvel élément au conteneur mutating func append(_ item: ItemType) // Obtient le nombre d’éléments dans le conteneur var count: Int { get } // Récupérer chaque élément du conteneur à l'aide d'un subscript avec un type d'index Int subscript(i: Int) -> ItemType { get } } // // Type TOS générique conforme au protocole Container struct Stack<Element>: Container { // Partie d'implémentation originale de Stack<Element> var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } // Partie d'implémentation du protocole Container mutating func append(_ item: Element) { self.push(item) } var count: Int { return items.count } subscript(i: Int) -> Element { return items[i] } } // Extension pour utiliser Array comme Container extension Array: Container {} func allItemsMatch<C1: Container, C2: Container> (_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.ItemType == C2.ItemType, C1.ItemType: Equatable { // Vérifier que deux conteneurs contiennent le même nombre d'éléments if someContainer.count != anotherContainer.count { return false } // Vérifier si chaque paire d'éléments est égale for i in 0..<someContainer.count { if someContainer[i] != anotherContainer[i] { return false } } // Tous les éléments correspondent, retourner true return true } var tos = Stack<String>() tos.push("google") tos.push("example") tos.push("taobao") var aos = ["google", "example", "taobao"] if allItemsMatch(tos, aos) { print("Tous les éléments correspondent") } else { print("Les éléments ne correspondent pas") }

La sortie de l'exécution du programme ci-dessus est :

Match all elements
Autres extensions