Introdução: Por que não dá para viver sem Generics em Swift?
Imagine que você precisa escrever uma função que soma dois números. Depois — uma função para somar duas strings. E depois — para arrays. Sem programação genérica (Generics), você teria que criar uma versão separada do código para cada tipo de dado. Isso não só aumenta o volume de código, mas também dificulta sua manutenção.
Generics em Swift — é uma ferramenta poderosa que permite escrever código flexível, reutilizável e type-safe. Eles são a base de estruturas populares como Array, Dictionary e Optional. Neste artigo, vamos entender por que os Generics são necessários, como usá-los na prática e por que todo desenvolvedor iOS/macOS iniciante deve dominar este tópico.
O que são Generics em palavras simples?
Generics — é uma forma de escrever código que funciona com qualquer tipo de dado, sem sacrificar a tipagem forte do Swift. Em vez de especificar um tipo concreto (por exemplo, Int ou String), você usa um placeholder (geralmente a letra T), que é substituído pelo tipo real durante a chamada da função ou criação da estrutura.
O problema sem Generics
Suponha que você escreva uma função para trocar os valores de duas variáveis. Sem Generics, você teria 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
}
Isso é duplicação evidente. Se precisar trocar Double ou tipos personalizados, a quantidade de funções vai crescer.
Solução com Generics
Uma função universal substituirá todas as anteriores:
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
// Uso:
var x = 10
var y = 20
swapValues(&x, &y) // x = 20, y = 10
var firstName = "Ana"
var lastName = "Ivanova"
swapValues(&firstName, &lastName) // firstName = "Ivanova"
Os colchetes angulares <T> declaram o placeholder do tipo. O compilador determina automaticamente que T é Int no primeiro caso e String no segundo. O código fica conciso e seguro.
Como usar Generics: cenários principais
Generics em Swift são usados em funções, estruturas, classes, enums e protocolos. Vamos ver os casos mais comuns.
1. Funções genéricas
Já vimos o exemplo com swapValues. Mas podemos ir além — criar uma função que encontra o índice de um elemento em um 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, 9, 12]
if let index = findIndex(of: 9, in: numbers) {
print("Índice: \(index)") // Índice: 2
}
Observe o <T: Equatable>. Isso é uma restrição de tipo (type constraint). Estamos dizendo: "T deve estar em conformidade com o protocolo Equatable para que possamos usar o operador ==". Sem essa restrição, o código não compila.
2. Tipos genéricos (structs e classes)
Vamos criar uma estrutura simples de "Pilha" (LIFO) que pode armazenar qualquer tipo:
struct Stack<Element> {
private var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element? {
return items.popLast()
}
func peek() -> Element? {
return items.last
}
}
// Uso:
var intStack = Stack<Int>()
intStack.push(1)
intStack.push(2)
print(intStack.pop
```