O que são Generics em Swift e para que servem
\n\nImagine que você está escrevendo uma função que troca dois números. Depois, você precisa da mesma função para strings. E depois, para datas. Se não existissem Generics (genéricos), você teria que escrever três funções idênticas, diferindo apenas nos tipos de dados. Isso viola o princípio DRY (Don't Repeat Yourself) e torna o código volumoso.
\n\nGenerics (genéricos ou tipos genéricos) é um mecanismo poderoso em Swift que permite escrever código flexível e reutilizável que funciona com qualquer tipo de dado. Em vez de especificar um tipo concreto, você usa um placeholder (marcador), que é substituído pelo tipo real ao chamar a função ou criar a estrutura.
\n\nNeste artigo, vamos explorar como os Generics funcionam em Swift e mostrar com exemplos práticos como usá-los no desenvolvimento iOS.
\n\n1. Fundamentos de Generics: funções e tipos
\n\nVamos começar com um exemplo simples. Vamos escrever uma função que troca dois valores:
\n\n// Sem Generics — precisa escrever para cada tipo separadamente\nfunc swapInts(_ a: inout Int, _ b: inout Int) {\n let temp = a\n a = b\n b = temp\n}\n\nfunc swapStrings(_ a: inout String, _ b: inout String) {\n let temp = a\n a = b\n b = temp\n}\n\n// Com Generics — uma função para todos os tipos\nfunc swapValues<T>(_ a: inout T, _ b: inout T) {\n let temp = a\n a = b\n b = temp\n}\n\nvar x = 10, y = 20\nswapValues(&x, &y) // Funciona com Int\nprint("x: \\(x), y: \\(y)") // x: 20, y: 10\n\nvar str1 = "Hello", str2 = "World"\nswapValues(&str1, &str2) // Funciona com String\nprint("str1: \\(str1), str2: \\(str2)") // str1: World, str2: Hello\n\nAqui, <T> é um parâmetro de tipo universal. A letra T é apenas uma convenção (da palavra Type), você pode usar qualquer nome: <Element>, <ValueType>, etc.
Generics funcionam não apenas com funções, mas também com tipos. Vamos considerar uma estrutura de contêiner simples:
\n\n// Estrutura genérica para armazenar um único valor\nstruct Box<T> {\n let value: T\n \n init(_ value: T) {\n self.value = value\n }\n}\n\nlet intBox = Box(42) // Tipo Box<Int>\nlet stringBox = Box("Swift") // Tipo Box<String>\nlet arrayBox = Box([1, 2, 3]) // Tipo Box<[Int]>\n\nprint(intBox.value) // 42\nprint(stringBox.value) // Swift\n\nO compilador Swift determina automaticamente o tipo T com base no valor passado. Isso é chamado de type inference (inferência de tipo).
2. Restrições de tipo (Type Constraints)
\n\nÀs vezes, é necessário restringir quais tipos podem ser usados com sua generalização. Por exemplo, você quer escrever uma função que encontra o valor máximo em um array. Mas o operador > não funciona com todos os tipos — apenas com aqueles que estão em conformidade (conform) com o protocolo Comparable.
// Restrição: T deve estar em conformidade com o protocolo Comparable\nfunc findMax<T: Comparable>(in array: [T]) -> T? {\n guard !array.isEmpty else { return nil }\n \n var maxValue = array[0]\n for element in array {\n if element > maxValue {\n maxValue = element\n }\n }\n return maxValue\n}\n\nlet numbers = [3, 7, 1, 9, 5]\nprint(findMax(in: numbers) ?? "No max") // 9\n\nlet words = ["apple", "zebra", "banana"]\nprint(findMax(in: words) ?? "No max") // "zebra" (em ordem alfabética)\n\nA sintaxe <T: Comparable> significa: "O tipo T deve estar em conformidade com o protocolo Comparable". Você pode adicionar várias restrições separadas por vírgula ou usar where para condições mais complexas:
// Várias restrições usando where\nfunc process<T>(_ value: T) where T: Numeric, T: Comparable {\n if value > 0 {\n print("Positivo")