Introducción: qué son los Generics y por qué son importantes en Swift
Los Generics (tipos genéricos) son una de las características más potentes y elegantes del lenguaje Swift. Permiten escribir código flexible y reutilizable que funciona con cualquier tipo de datos, manteniendo al mismo tiempo un tipado estricto. Si alguna vez has usado Array<Int>, Dictionary<String, Any> o Optional<T>, ya te has encontrado con los Generics.
En este artículo analizaremos para qué sirven los Generics en Swift, cómo funcionan en la práctica y mostraremos ejemplos para el desarrollo en iOS/macOS. El material será útil tanto para principiantes como para desarrolladores experimentados que deseen estructurar sus conocimientos.
1. ¿Para qué sirven los Generics en Swift?
El objetivo principal de los Generics es evitar la duplicación de código y aumentar su seguridad. Imagina que necesitas escribir una función que intercambie dos valores. Sin Generics, tendrías que crear funciones separadas para Int, String, Double, y así sucesivamente:
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, esta tarea se resuelve con una única función universal:
func swapValues<T>(_ a: inout T, _ b: inout T) { let temp = a a = b b = temp}
var x = 10, y = 20swapValues(&x, &y) // x = 20, y = 10
var name1 = "Alice", name2 = "Bob"swapValues(&name1, &name2) // name1 = "Bob", name2 = "Alice"Ventajas de los Generics:
- Reutilización de código: una función/tipo funciona con cualquier tipo de datos.
- Seguridad de tipos: el compilador verifica los tipos en tiempo de compilación, eliminando errores en tiempo de ejecución.
- Rendimiento: los Generics en Swift se implementan mediante polimorfismo en tiempo de compilación (especialización), por lo que no hay sobrecarga por conversiones de tipos.
- Legibilidad: el código se vuelve más abstracto y conciso.
2. Funciones Genéricas: sintaxis y ejemplos
Una función genérica se declara usando corchetes angulares <T>, donde T es un placeholder (marcador de posición) para el tipo. Puedes usar cualquier identificador: T, Element, Value, etc.
Ejemplo básico: búsqueda del índice de un elemento
func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? { for (index, item) in array.enumerated() { if item == value { return index } } return nil}
let numbers = [1, 3, 5, 7, 9]if let index = findIndex(of: 5, in: numbers) { print("Índice del número 5: \\(index)") // Índice del número 5: 2}Observa la restricción <T: Equatable>. Esto es un type constraint (restricción de tipo). Le indica al compilador: "T debe cumplir con el protocolo Equatable". Sin esta restricción, el operador == no estaría disponible.
Múltiples parámetros de tipo
Puedes usar varios parámetros de tipo:
func combine<T, U>(_ first: T, with second: U) -> String { return "\\(first) + \\(second)"}
let result = combine(42, with: "respuesta")print(result) // 42 + respuesta3. Tipos Genéricos: creación de estructuras y clases genéricas
Los Generics no solo se aplican a funciones, sino también a tipos. El ejemplo más popular es Array<Element>. Creemos nuestra propia estructura genérica: una pila (LIFO):
struct Stack<Element> { private var items: [Element] = [] mutating func push(_ item: Element) { items.append(item
```