wandres.dev
EL MODELO DE TIPOS · structs, enums, protocolos

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

⏱ 13 min

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.

🎯 Al terminar esta lección sabrás
  • 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")
}
Por qué SwiftUI usa 'some View'

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.

💡
No abuses de los genéricos

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.

⚔️ Generaliza con cabeza
  1. Escribe una función genérica que devuelva el primer elemento de un array ([T] -> T?).
  2. Crea un tipo genérico Caja<T> que guarde un valor y lo devuelva.
  3. Escribe una función <T: Comparable> que devuelva el mínimo de dos valores.
  4. Identifica en la documentación de SwiftUI dónde aparece some View.