Swift Language
Génériques
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
etDictionary
de Swift sont tous deux des collections génériques. Vous pouvez créer un tableauInt
valeursInt
ou un tableauString
valeursString
, 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.
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")
}
}
}