Recherche…


Remarques

Le code générique vous permet d'écrire des fonctions et des types flexibles et réutilisables pouvant fonctionner avec n'importe quel type, sous réserve des exigences que vous définissez. Vous pouvez écrire un code qui évite la duplication et exprime son intention de manière claire et abstraite.

Les génériques sont l'une des fonctionnalités les plus puissantes de Swift, et une grande partie de la bibliothèque standard Swift est construite avec un code générique. Par exemple, les types Array et Dictionary de Swift sont tous deux des collections génériques. Vous pouvez créer un tableau Int valeurs Int ou un tableau String valeurs String , ou un tableau pour tout autre type pouvant être créé dans Swift. De même, vous pouvez créer un dictionnaire pour stocker les valeurs de tout type spécifié, et il n'y a pas de limitation quant à ce type.

Source: Langage de programmation rapide d'Apple

Restriction des types d'espaces génériques

Il est possible de forcer les paramètres de type d’une classe générique à implémenter un protocole , par exemple, Equatable

class MyGenericClass<Type: Equatable>{
    
    var value: Type
    init(value: Type){
        self.value = value
    }
    
    func getValue() -> Type{
        return self.value
    }

    func valueEquals(anotherValue: Type) -> Bool{
        return self.value == anotherValue
    }
}

Chaque fois que nous créons une nouvelle MyGenericClass , le paramètre type doit implémenter le protocole Equatable (en veillant à ce que le paramètre type puisse être comparé à une autre variable du même type utilisant == )

let myFloatGeneric = MyGenericClass<Double>(value: 2.71828) // valid
let myStringGeneric = MyGenericClass<String>(value: "My String") // valid

// "Type [Int] does not conform to protocol 'Equatable'"
let myInvalidGeneric = MyGenericClass<[Int]>(value: [2]) 

let myIntGeneric = MyGenericClass<Int>(value: 72)
print(myIntGeneric.valueEquals(72)) // true
print(myIntGeneric.valueEquals(-274)) // false

// "Cannot convert value of type 'String' to expected argument type 'Int'"
print(myIntGeneric.valueEquals("My String"))

Les bases des génériques

Les génériques sont des espaces réservés aux types, ce qui vous permet d'écrire un code flexible pouvant être appliqué à plusieurs types. L'avantage de l'utilisation de génériques sur Any est qu'ils permettent toujours au compilateur d'appliquer une sécurité de type forte.

Un espace réservé générique est défini entre crochets <> .

Fonctions génériques

Pour les fonctions , cet espace réservé est placé après le nom de la fonction:

/// Picks one of the inputs at random, and returns it
func pickRandom<T>(_ a:T, _ b:T) -> T {
    return arc4random_uniform(2) == 0 ? a : b
}

Dans ce cas, le générique est T Lorsque vous appelez la fonction, Swift peut déduire le type de T pour vous (car il agit simplement comme un espace réservé pour un type réel).

let randomOutput = pickRandom(5, 7) // returns an Int (that's either 5 or 7)

Nous passons ici deux entiers à la fonction. Par conséquent, Swift en déduit T == Int - la signature de la fonction est donc supposée être (Int, Int) -> Int .

En raison de la sécurité de type forte offerte par les génériques, les arguments et le retour de la fonction doivent être du même type. Par conséquent, les éléments suivants ne seront pas compilés:

struct Foo {}

let foo = Foo()

let randomOutput = pickRandom(foo, 5) // error: cannot convert value of type 'Int' to expected argument type 'Foo'

Types génériques

Pour pouvoir utiliser des génériques avec des classes , des structures ou des énumérations , vous pouvez définir l'espace réservé générique après le nom du type.

class Bar<T> {
    var baz : T
    
    init(baz:T) {
        self.baz = baz
    }
}

Ce paramètre générique nécessitera un type lorsque vous utiliserez la classe Bar . Dans ce cas, il peut être déduit de l'initialiseur init(baz:T) .

let bar = Bar(baz: "a string") // bar's type is Bar<String>

Ici, l'espace réservé générique T est supposé être de type String , créant ainsi une instance Bar<String> . Vous pouvez également spécifier le type explicitement:

let bar = Bar<String>(baz: "a string")

Lorsqu'il est utilisé avec un type, l'espace réservé générique donné conservera son type pendant toute la durée de vie de l'instance donnée et ne peut pas être modifié après l'initialisation. Par conséquent, lorsque vous accédez à la propriété baz , il sera toujours de type String pour cette instance donnée.

let str = bar.baz // of type String

Passage de types génériques

Lorsque vous transmettez des types génériques, vous devez, dans la plupart des cas, être explicite sur le type générique que vous attendez. Par exemple, comme entrée de fonction:

func takeABarInt(bar:Bar<Int>) {
    ...
}

Cette fonction n'acceptera qu'une Bar<Int> . Si vous tentez de transmettre une instance de la Bar où le type générique de l'espace réservé n'est pas Int vous Int une erreur de compilation.

Nom générique d'espace réservé

Les noms génériques d'espace réservé ne se limitent pas à des lettres simples. Si un espace réservé donné représente un concept significatif, vous devez lui donner un nom descriptif. Par exemple, le Array de Swift a un espace réservé générique appelé Element , qui définit le type d'élément d'une instance de Array donnée.

public struct Array<Element> : RandomAccessCollection, MutableCollection {
    ...
}

Exemples de classes génériques

Une classe générique avec le paramètre type Type

class MyGenericClass<Type>{
 
    var value: Type
    init(value: Type){
        self.value = value
    }
 
    func getValue() -> Type{
        return self.value
    }
 
    func setValue(value: Type){
        self.value = value
    }
}

Nous pouvons maintenant créer de nouveaux objets en utilisant un paramètre de type

let myStringGeneric = MyGenericClass<String>(value: "My String Value")
let myIntGeneric = MyGenericClass<Int>(value: 42)
 
print(myStringGeneric.getValue()) // "My String Value"
print(myIntGeneric.getValue()) // 42
 
myStringGeneric.setValue("Another String")
myIntGeneric.setValue(1024)
 
print(myStringGeneric.getValue()) // "Another String"
print(myIntGeneric.getValue()) // 1024

Les génériques peuvent également être créés avec plusieurs paramètres de type

class AnotherGenericClass<TypeOne, TypeTwo, TypeThree>{
 
    var value1: TypeOne
    var value2: TypeTwo
    var value3: TypeThree
    init(value1: TypeOne, value2: TypeTwo, value3: TypeThree){
        self.value1 = value1
        self.value2 = value2
        self.value3 = value3
    }
 
    func getValueOne() -> TypeOne{return self.value1}
    func getValueTwo() -> TypeTwo{return self.value2}
    func getValueThree() -> TypeThree{return self.value3}
}

Et utilisé de la même manière

let myGeneric = AnotherGenericClass<String, Int, Double>(value1: "Value of pi", value2: 3, value3: 3.14159)
 
print(myGeneric.getValueOne() is String) // true
print(myGeneric.getValueTwo() is Int) // true
print(myGeneric.getValueThree() is Double) // true
print(myGeneric.getValueTwo() is String) // false
 
print(myGeneric.getValueOne()) // "Value of pi"
print(myGeneric.getValueTwo()) // 3
print(myGeneric.getValueThree()) // 3.14159

Héritage de classe générique

Les classes génériques peuvent être héritées:

// Models
class MyFirstModel {
}

class MySecondModel: MyFirstModel {
}

// Generic classes
class MyFirstGenericClass<T: MyFirstModel> {
    
    func doSomethingWithModel(model: T) {
        // Do something here
    }
    
}

class MySecondGenericClass<T: MySecondModel>: MyFirstGenericClass<T> {
    
    override func doSomethingWithModel(model: T) {
        super.doSomethingWithModel(model)
        
        // Do more things here
    }
    
}

Utilisation de génériques pour simplifier les fonctions de tableau

Une fonction qui étend la fonctionnalité du tableau en créant une fonction de suppression orientée objet.

// Need to restrict the extension to elements that can be compared.
// The `Element` is the generics name defined by Array for its item types.
// This restriction also gives us access to `index(of:_)` which is also
// defined in an Array extension with `where Element: Equatable`.
public extension Array where Element: Equatable {
    /// Removes the given object from the array.
    mutating func remove(_ element: Element) {
        if let index = self.index(of: element ) {
            self.remove(at: index)
        } else {
            fatalError("Removal error, no such element:\"\(element)\" in array.\n")
        }
    }
}

Usage

var myArray = [1,2,3]
print(myArray)

// Prints [1,2,3]

Utilisez la fonction pour supprimer un élément sans avoir besoin d'index. Il suffit de passer l'objet à supprimer.

myArray.remove(2)
print(myArray)

// Prints [1,3]

Utiliser des génériques pour améliorer la sécurité des types

Prenons cet exemple sans utiliser de génériques

protocol JSONDecodable {
    static func from(_ json: [String: Any]) -> Any?
}

La déclaration de protocole semble correcte, sauf si vous l'utilisez réellement.

let myTestObject = TestObject.from(myJson) as? TestObject

Pourquoi devez-vous lancer le résultat dans TestObject ? En raison du type de retour Any dans la déclaration de protocole.

En utilisant des génériques, vous pouvez éviter ce problème qui peut provoquer des erreurs d’exécution (et nous ne voulons pas les avoir!)

protocol JSONDecodable {
    associatedtype Element 
    static func from(_ json: [String: Any]) -> Element?
}

struct TestObject: JSONDecodable {
    static func from(_ json: [String: Any]) -> TestObject? {
    }
}

let testObject = TestObject.from(myJson) // testObject is now automatically `TestObject?`

Contraintes de type avancées

Il est possible de spécifier plusieurs contraintes de type pour les génériques en utilisant la clause where :

func doSomething<T where T: Comparable, T: Hashable>(first: T, second: T) {
    // Access hashable function
    guard first.hashValue == second.hashValue else {
        return
    }
    // Access comparable function
    if first == second {
        print("\(first) and \(second) are equal.")
    }
}

Il est également valable d'écrire la clause where après la liste des arguments:

func doSomething<T>(first: T, second: T) where T: Comparable, T: Hashable {
    // Access hashable function
    guard first.hashValue == second.hashValue else {
        return
    }
    // Access comparable function
    if first == second {
        print("\(first) and \(second) are equal.")
    }
}

Les extensions peuvent être limitées à des types satisfaisant aux conditions. La fonction est uniquement disponible pour les instances qui satisfont aux conditions de type:

// "Element" is the generics type defined by "Array". For this example, we
// want to add a function that requires that "Element" can be compared, that
// is: it needs to adhere to the Equatable protocol.
public extension Array where Element: Equatable {
    /// Removes the given object from the array.
    mutating func remove(_ element: Element) {
        // We could also use "self.index(of: element)" here, as "index(of:_)"
        // is also defined in an extension with "where Element: Equatable".
        // For the sake of this example, explicitly make use of the Equatable.
        if let index = self.index(where: { $0 == element }) {
            self.remove(at: index)
        } else {
            fatalError("Removal error, no such element:\"\(element)\" in array.\n")
        }
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow