Generics em Swift: para que servem e como usar na prática
Generics (programação genérica) — é um dos recursos mais poderosos e fundamentais da linguagem Swift. Eles permitem escrever código flexível, type-safe e reutilizável, evitando duplicação. Se você já usou Array<Int>, Dictionary<String, Any> ou Optional<T>, já encontrou generics. Neste artigo, vamos entender para que servem, como aplicá-los corretamente e ver exemplos práticos para desenvolvimento iOS/macOS.
Para que servem os Generics em Swift?
O principal problema que os generics resolvem é a necessidade de escrever o mesmo código para diferentes tipos de dados. Sem eles, você teria que criar funções separadas para trabalhar com Int, String, Double e estruturas personalizadas. Isso leva à duplicação de código, erros e dificuldade de manutenção.
Principais vantagens dos generics:
- Type-safety: o compilador verifica os tipos em tempo de compilação, prevenindo erros em tempo de execução.
- Reutilização de código: uma função ou tipo funciona com qualquer tipo de dado.
- Desempenho: generics em Swift são implementados através de especialização — o compilador cria código otimizado para cada tipo específico, comparável à escrita manual de código duplicado.
- Legibilidade e abstração: o código se torna mais conciso e expressivo.
Vamos ver um exemplo simples. Suponha que precisamos de uma função para trocar os valores de duas variáveis. Sem generics, teríamos que escrever assim:
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}// ... e assim para cada tipoCom generics, a solução se torna universal:
func swapValues<T>(_ a: inout T, _ b: inout T) { let temp = a a = b b = temp}
var x = 10var y = 20swapValues(&x, &y) // Funciona com Int
var str1 = "Hello"var str2 = "World"swapValues(&str1, &str2) // Funciona com StringComo você pode ver, <T> é um placeholder (marcador) para o tipo. O compilador infere automaticamente o tipo específico na chamada da função.
Funções e tipos genéricos
Generics podem ser usados não apenas com funções, mas também com tipos personalizados: structs, classes e enums.
Funções genéricas
Além do placeholder simples, é possível impor restrições ao tipo. Por exemplo, uma função que funciona apenas com tipos que implementam o protocolo Equatable:
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, 5, 3, 12]if let index = findIndex(of: 5, in: numbers) { print("Índice encontrado: \\(index)") // Exibirá: Índice encontrado: 1}A restrição <T: Equatable> garante que o operador == está definido para o tipo T.
Tipos genéricos (structs e classes)
Vamos criar uma pilha simples — uma estrutura de dados que funciona no princípio LIFO (Last In, First Out):
struct Stack<Element> { private var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element? { return items.popLast() } var top: Element? { return items.last }}
var intStack = Stack<Int>()intStack.push(10)intStack.push(20)print(intStack.pop() ?? 0) // 20
var str