¿Qué son los Generics en Swift y para qué sirven?
Los Generics (programación genérica) son una de las características más potentes del lenguaje Swift. Permiten escribir código flexible y reutilizable que funciona con cualquier tipo de dato, manteniendo al mismo tiempo un tipado estricto. En lugar de crear funciones o estructuras separadas para Int, String, Double y otros tipos, escribes un único código universal que se adapta al tipo específico durante la compilación.
Imagina que necesitas escribir una función que intercambie dos valores. Sin Generics, tendrías que crear sobrecargas para cada tipo:
func swapInts(_ a: inout Int, _ b: inout Int) { let temp = a a = b b = temp}
func swapStrings(_ a: inout String, _ b: inout String) { let temp = a a = b b = temp}Con Generics esto se hace en una sola línea:
func swapValues<T>(_ a: inout T, _ b: inout T) { let temp = a a = b b = temp}
var x = 10, y = 20swapValues(&x, &y) // funciona con Int
var str1 = "Hello", str2 = "World"swapValues(&str1, &str2) // funciona con StringPrincipales ventajas de los Generics
- Seguridad de tipos: el compilador verifica los tipos en tiempo de compilación, eliminando errores en tiempo de ejecución
- Reutilización de código: el código escrito una vez funciona con cualquier tipo
- Rendimiento: los Generics en Swift se implementan a nivel de compilador, sin pérdida de velocidad de ejecución
- Legibilidad: reduce la duplicación de código, haciéndolo más conciso
Funciones Generic y parámetros de tipo
Una función Generic se declara usando corchetes angulares <>, dentro de los cuales se especifica el parámetro de tipo (generalmente denotado con la letra T, pero se puede usar cualquier nombre). El parámetro de tipo se convierte en un placeholder que se reemplaza por el tipo real al llamar a la función.
Ejemplo: búsqueda de un elemento en un array
func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? { for (index, element) in array.enumerated() { if element == value { return index } } return nil}
let numbers = [1, 5, 3, 12, 7]if let index = findIndex(of: 12, in: numbers) { print("Índice del número 12: \\(index)") // Índice del número 12: 3}
let names = ["Ana", "Boris", "Víctor"]if let index = findIndex(of: "Boris", in: names) { print("Índice del nombre Boris: \\(index)") // Índice del nombre Boris: 1}Observa la restricción T: Equatable. Esto es un type constraint — requerimos que el tipo T soporte el protocolo Equatable, de lo contrario la operación de comparación == sería imposible.
Tipos Generic: estructuras, clases y enumeraciones
Los Generics no solo funcionan con funciones, sino también con tipos personalizados. Esto permite crear contenedores de datos universales.
Creación de un Stack Generic
Un ejemplo clásico es la estructura de datos Stack (pila), que funciona con cualquier tipo:
struct Stack<Element> { private var items: [Element] = [] mutating func push(_ item: Element) { items.append(item) } @discardableResult mutating func pop() -> Element? { return items.popLast() } func peek() -> Element? { return items.last } var isEmpty: Bool { return items.isEmpty }}
// Usovar intStack = Stack<Int>()intStack.push(10)intStack.push(20)print(intStack.pop() ?? "") // 20
var stringStack = Stack<String>()stringStack.push("Swift")stringStack.push("Generics")print(stringStack.pop() ?? "") // GenericsEnumeraciones Generic
Las enumeraciones también pueden ser