Buscar..


Observaciones

El código genérico le permite escribir funciones y tipos flexibles y reutilizables que pueden funcionar con cualquier tipo, sujeto a los requisitos que usted defina. Puede escribir código que evite la duplicación y exprese su intención de una manera clara y abstracta.

Los genéricos son una de las características más poderosas de Swift, y gran parte de la biblioteca estándar de Swift está construida con código genérico. Por ejemplo, los tipos Array y Dictionary Swift son colecciones genéricas. Puede crear una matriz que contenga valores de Int , o una matriz que contenga valores de String , o incluso una matriz para cualquier otro tipo que pueda crearse en Swift. De manera similar, puede crear un diccionario para almacenar valores de cualquier tipo específico, y no hay limitaciones sobre lo que ese tipo puede ser.

Fuente: lenguaje de programación Swift de Apple

Restricción de tipos genéricos de marcadores de posición

Es posible forzar los parámetros de tipo de una clase genérica para implementar un protocolo , por ejemplo, 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
    }
}

Cada vez que creamos una nueva MyGenericClass , el parámetro type debe implementar el protocolo Equatable (asegurando que el parámetro type pueda compararse con otra variable del mismo tipo usando == )

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"))

Los fundamentos de los genéricos

Los genéricos son marcadores de posición para los tipos, lo que le permite escribir código flexible que se puede aplicar a varios tipos. La ventaja de usar los genéricos en lugar de Any es que aún permiten que el compilador imponga una seguridad de tipo sólida.

Un marcador de posición genérico se define entre paréntesis angulares <> .

Funciones genéricas

Para las funciones , este marcador de posición se coloca después del nombre de la función:

/// 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
}

En este caso, el marcador de posición genérico es T Cuando vienes a llamar a la función, Swift puede inferir el tipo de T para ti (ya que simplemente actúa como un marcador de posición para un tipo real).

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

Aquí estamos pasando dos enteros a la función. Por lo tanto, Swift está inferiendo T == Int : por lo tanto, la firma de función se infiere que es (Int, Int) -> Int .

Debido al tipo fuerte de seguridad que ofrecen los genéricos, tanto los argumentos como el retorno de la función deben ser del mismo tipo. Por lo tanto lo siguiente no se compilará:

struct Foo {}

let foo = Foo()

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

Tipos genéricos

Para utilizar los genéricos con clases , estructuras o enumeraciones , puede definir el marcador de posición genérico después del nombre de tipo.

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

Este marcador de posición genérico requerirá un tipo cuando venga a usar la Bar clase. En este caso, se puede inferir del inicializador init(baz:T) .

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

Aquí se infiere que el marcador de posición T genérico es de tipo String , creando así una instancia de Bar<String> . También puede especificar el tipo explícitamente:

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

Cuando se usa con un tipo, el marcador de posición genérico dado mantendrá su tipo durante toda la vida útil de la instancia dada, y no podrá cambiarse después de la inicialización. Por lo tanto, cuando accede a la propiedad baz , siempre será de tipo String para esta instancia dada.

let str = bar.baz // of type String

Pasando por tipos genéricos

Cuando llega a pasar por tipos genéricos, en la mayoría de los casos debe ser explícito sobre el tipo de marcador de posición genérico que espera. Por ejemplo, como una entrada de función:

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

Esta función solo aceptará una Bar<Int> . Intentar pasar en una instancia de Bar donde el tipo de marcador de posición genérico no es Int dará como resultado un error del compilador.

Nombre genérico de marcador de posición

Los nombres genéricos de los marcadores de posición no se limitan a letras individuales. Si un marcador de posición dado representa un concepto significativo, debe darle un nombre descriptivo. Por ejemplo, Swift's Array tiene un marcador de posición genérico llamado Element , que define el tipo de elemento de una instancia de Array dada.

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

Ejemplos genéricos de clase

Una clase genérica con el parámetro tipo 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
    }
}

Ahora podemos crear nuevos objetos usando un parámetro de tipo

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

Los genéricos también se pueden crear con múltiples parámetros de tipo

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}
}

Y usado de la misma manera.

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

Herencia genérica de clase

Las clases genéricas pueden ser heredadas:

// 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
    }
    
}

Usando Genéricos para Simplificar las Funciones de Arreglos

Una función que amplía la funcionalidad de la matriz creando una función de eliminación orientada a objetos.

// 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")
        }
    }
}

Uso

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

// Prints [1,2,3]

Utilice la función para eliminar un elemento sin necesidad de un índice. Sólo pasa el objeto para eliminar.

myArray.remove(2)
print(myArray)

// Prints [1,3]

Use genéricos para mejorar la seguridad de tipo

Tomemos este ejemplo sin usar genéricos.

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

La declaración de protocolo parece estar bien a menos que realmente la uses.

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

¿Por qué tienes que lanzar el resultado a TestObject ? Debido a Any tipo de retorno en la declaración de protocolo.

Al usar los genéricos, puede evitar este problema que puede causar errores de tiempo de ejecución (¡y no queremos tenerlos!)

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?`

Restricciones de tipo avanzado

Es posible especificar varias restricciones de tipo para genéricos usando la cláusula 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.")
    }
}

También es válido escribir la cláusula where después de la lista de argumentos:

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.")
    }
}

Las extensiones se pueden restringir a tipos que satisfacen condiciones. La función solo está disponible para instancias que cumplan con las condiciones de tipo:

// "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
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow