Genéricos: código que funciona con cualquier tipo
Escribe una vez, úsalo con cualquier tipo, sin perder seguridad. Funciones y tipos genéricos, restricciones y una introducción a los tipos opacos (some).
Los genéricos te dejan escribir código reutilizable sin sacrificar la seguridad de tipos. Es lo que hace que Array funcione igual con Int que con String, y lo que usarás para construir componentes flexibles. Menos duplicación, misma seguridad.
- Funciones genéricas.
- Tipos genéricos.
- Restricciones con protocolos.
- Tipos opacos (
some) y su papel en SwiftUI.
El problema que resuelven
Sin genéricos, escribirías la misma función para cada tipo. Con ellos, un marcador de tipo (por convención T) representa “cualquier tipo”:
func intercambiar<T>(_ a: inout T, _ b: inout T) {
let temp = a; a = b; b = temp
}
var x = 1, y = 2
intercambiar(&x, &y) // funciona con Int
var s1 = "a", s2 = "b"
intercambiar(&s1, &s2) // …y con String, sin reescribir nada
El <T> declara el parámetro de tipo. Swift lo deduce en cada llamada y mantiene la seguridad: no puedes mezclar tipos por error.
Tipos genéricos
También los tipos pueden ser genéricos. Una pila (Stack) que sirva para cualquier elemento:
struct Pila<Elemento> {
private var items: [Elemento] = []
mutating func apilar(_ x: Elemento) { items.append(x) }
mutating func desapilar() -> Elemento? { items.popLast() }
var cima: Elemento? { items.last }
}
var numeros = Pila<Int>()
numeros.apilar(1)
numeros.apilar(2)
print(numeros.desapilar()) // Optional(2)
Array<T>, Dictionary<K, V> y Optional<T> que ya usas son, precisamente, tipos genéricos de la librería estándar.
Restricciones: exigir capacidades
A menudo el tipo genérico necesita poder hacer algo (comparar, sumar…). Lo exiges con una restricción de protocolo tras :
func maximo<T: Comparable>(_ lista: [T]) -> T? {
lista.max()
}
print(maximo([3, 9, 2])) // Optional(9)
print(maximo(["a", "z", "m"])) // Optional("z")
<T: Comparable> significa “cualquier T, siempre que sea comparable”. Aquí es donde protocolos y genéricos se dan la mano.
Tipos opacos: some
Un tipo opaco, con la palabra some, dice “devuelvo un tipo concreto que conforma a este protocolo, pero no te digo cuál”. Es la base de la firma de toda vista de SwiftUI:
func hacerVista() -> some View {
Text("Hola")
}
Cuando escribas SwiftUI verás var body: some View por todas partes. Significa: “el cuerpo es algún tipo concreto de vista —quizá un VStack con Text dentro—, pero el detalle no importa; solo importa que es una View”. some te da la flexibilidad de los protocolos con el rendimiento y la seguridad de un tipo concreto. No necesitas dominarlo ahora; basta con reconocerlo cuando aparezca en el Nivel 2.
Los genéricos son para código reutilizable de verdad (contenedores, utilidades, librerías). Para la lógica normal de tu app, tipos concretos suelen ser más claros. Alcánzalos cuando notes que estás duplicando la misma lógica para tipos distintos.
- Escribe una función genérica que devuelva el primer elemento de un array (
[T] -> T?). - Crea un tipo genérico
Caja<T>que guarde un valor y lo devuelva. - Escribe una función
<T: Comparable>que devuelva el mínimo de dos valores. - Identifica en la documentación de SwiftUI dónde aparece
some View.