Swift: Tutorial de Genéricos do Swift

Os genéricos permitem escrever código flexível que funciona com qualquer tipo, evitando a necessidade de duplicar a mesma lógica para cada tipo -- como um molde universal que produz a forma correta independentemente do material derramado.

1. O Que Você Vai Aprender


2. Uma História Real de um Engenheiro Back-End

(1) Problema: Escrever a Mesma Lógica para Cada Tipo

Charlie estava construindo um serviço de cache e precisava de uma estrutura de dados de pilha último-a-entrar-primeiro-a-sair. Ele primeiro escreveu uma versão para Int:

SWIFT
struct IntStack {
    private var items: [Int] = []
    mutating func push(_ item: Int) { items.append(item) }
    mutating func pop() -> Int? { items.popLast() }
}

Mas logo ele precisou suportar String, Double e até um tipo Usuario personalizado. Charlie se viu copiando e colando -- apenas o nome do tipo mudava; a lógica era idêntica.

(2) A Solução Genérica

Genéricos transformam o tipo em um parâmetro -- uma implementação serve para todos:

SWIFT
struct Stack<Element> {
    private var items: [Element] = []
    mutating func push(_ item: Element) { items.append(item) }
    mutating func pop() -> Element? { items.popLast() }
}

Agora Pilha<Int>, Pilha<String> e Pilha<Usuario> usam a mesma implementação, com o compilador garantindo a segurança de tipo.

(3) Benefícios: 60% Menos Código

Dimensão Abordagem Não-Genérica Abordagem Genérica
Linhas de código 120 linhas (3 tipos) 20 linhas (1 genérico)
Custo de adicionar novo tipo 40 linhas de copiar-colar 1 linha: Pilha<NovoTipo>
Segurança de tipo Cada tipo independente Verificado em tempo de compilação
Custo de manutenção Alterar lógica em 3 lugares Alterar apenas em 1 lugar

3. Funções Genéricas

Funções genéricas permitem usar tipos placeholder nas definições de função; o tipo concreto é determinado no local da chamada.

100%
graph LR
    A["func trocar<T>(a: inout T, b: inout T)"] --> B["Chamada com Int"]
    A --> C["Chamada com String"]
    A --> D["Chamada com Double"]
    B --> E["T = Int: troca segura"]
    C --> F["T = String: troca segura"]
    D --> G["T = Double: troca segura"]

(1) Sintaxe de Parâmetro Genérico

Parâmetros genéricos são escritos entre chaves angulares <> após o nome da função, convencionalmente usando letras maiúsculas T, U, V como placeholders.

Sintaxe Significado Exemplo
<T> Parâmetro genérico único func identidade<T>(_ valor: T) -> T
<T, U> Dois parâmetros genéricos func par<T, U>(_ a: T, _ b: U) -> (T, U)
<T: Equatable> Genérico restrito func ehIgual<T: Equatable>(_ a: T, _ b: T) -> Bool

▶ Exemplo: Trocando Valores de Duas Variáveis

SWIFT
// ============================================
// Generic function: swap two values of any type
// ============================================
func swapValues<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}
var x = 10
var y = 20
swapValues(&x, &y)
print("x = \(x), y = \(y)")
var firstName = "Alice"
var lastName = "Bob"
swapValues(&firstName, &lastName)
print("firstName = \(firstName), lastName = \(lastName)")

Saída:

TEXT 📖 Somente leitura
x = 20, y = 10
firstName = Bob, lastName = Alice

(2) Múltiplos Parâmetros Genéricos

Funções podem ter múltiplos parâmetros genéricos, cada um representando um tipo diferente em uma posição diferente.

▶ Exemplo: Construindo um Par Chave-Valor

SWIFT
// ============================================
// Multiple generic parameters to build a key-value pair
// ============================================
func makePair<K, V>(_ key: K, _ value: V) -> (K, V) {
    return (key, value)
}
let pair1 = makePair("id", 1001)
let pair2 = makePair(3.14, "Pi")
print(pair1)
print(pair2)

Saída:

TEXT 📖 Somente leitura
(id, 1001)
(3.14, Pi)

4. Tipos Genéricos

Tipos genéricos permitem definir structs, classes ou enums com placeholders, permitindo que lidem com dados de qualquer tipo.

100%
graph TB
    A["Pilha<Elemento>"] --> B["empilhar(Elemento)"]
    A --> C["desempilhar() -> Elemento?"]
    A --> D["topo() -> Elemento?"]
    A --> E["contagem: Int"]
    B --> F["adicionar a itens"]
    C --> G["remover último de itens"]
    D --> H["retornar itens.last"]

(1) Structs Genéricas

SWIFT
struct Stack<Element> { ... }
Caso de Uso Sintaxe Descrição
Pilha de Int Pilha<Int> Só pode empilhar/desempilhar valores Int
Pilha de String Pilha<String> Só pode empilhar/desempilhar valores String
Pilha de tipo personalizado Pilha<Usuario> Só pode empilhar/desempilhar valores Usuario

▶ Exemplo: Estrutura de Dados Pilha Genérica

SWIFT
// ============================================
// Generic stack: supports last-in-first-out for any type
// ============================================
struct Stack<Element> {
    private var items: [Element] = []
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element? {
        items.popLast()
    }
    func peek() -> Element? {
        items.last
    }
    var count: Int { items.count }
}
var intStack = Stack<Int>()
intStack.push(10)
intStack.push(20)
intStack.push(30)
print("Pop: \(intStack.pop() ?? 0)")
var stringStack = Stack<String>()
stringStack.push("Alice")
stringStack.push("Bob")
print("Peek: \(stringStack.peek() ?? "")")

Saída:

TEXT 📖 Somente leitura
Pop: 30
Peek: Bob

(2) Extensões Genéricas

Ao estender um tipo genérico, você não precisa redeclarar o parâmetro de tipo -- basta usar o nome do placeholder diretamente.

▶ Exemplo: Adicionando um Método map à Pilha Genérica

SWIFT
// ============================================
// Adding map functionality to Stack via extension
// ============================================
extension Stack {
    func map<T>(_ transform: (Element) -> T) -> Stack<T> {
        var result = Stack<T>()
        for item in items {
            result.push(transform(item))
        }
        return result
    }
}
var s = Stack<Int>()
s.push(1); s.push(2); s.push(3)
let doubled = s.map { $0 * 2 }
print("Count: \(doubled.count)")

Saída:

TEXT 📖 Somente leitura
Count: 3

5. Restrições de Tipo e Tipos Associados

As restrições de tipo limitam os parâmetros genéricos para satisfazer condições específicas. Tipos associados são "placeholders genéricos" em protocolos, especificados pelo implementador.

100%
graph TB
    A["<T: Equatable>"] --> B["Pode usar =="]
    A --> C["func encontrarIndice<T: Equatable>(de: T, em: [T]) -> Int?"]
    B --> D["Int =="]
    B --> E["String =="]
    B --> F["Personalizado com Equatable"]

(1) Restrições de Tipo

Sintaxe de Restrição Significado Caso de Uso
<T: Equatable> T deve ser equacionável Encontrar elementos, desduplicação
<T: Hashable> T deve ser hasheável Chaves de dicionário
<T: Comparable> T deve ser comparável Ordenação, encontrar mín/máx
<T: Codable> T deve ser codável/decodável Serialização JSON

▶ Exemplo: Usando Restrição Equatable para Encontrar um Elemento

SWIFT
// ============================================
// Constraining generic parameter to be 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 = [10, 20, 30, 40]
if let index = findIndex(of: 30, in: numbers) {
    print("Found at index \(index)")
}
let names = ["Alice", "Bob", "Charlie"]
if let index = findIndex(of: "Bob", in: names) {
    print("Found at index \(index)")
}

Saída:

TEXT 📖 Somente leitura
Found at index 2
Found at index 1

(2) Tipos Associados e Cláusulas where

A palavra-chave associatedtype permite que protocolos suportem genéricos -- o próprio protocolo não especifica um tipo concreto; o implementador decide.

▶ Exemplo: Protocolo com Tipo Associado e Cláusula where

SWIFT
// ============================================
// Associated type protocol + where clause
// ============================================
protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
struct Box<T>: Container {
    typealias Item = T
    private var items: [T] = []
    mutating func append(_ item: T) { items.append(item) }
    var count: Int { items.count }
    subscript(i: Int) -> T { items[i] }
}
func allItemsMatch<C1: Container, C2: Container>(
    _ c1: C1, _ c2: C2
) -> Bool where C1.Item == C2.Item, C1.Item: Equatable {
    guard c1.count == c2.count else { return false }
    for i in 0..<c1.count {
        if c1[i] != c2[i] { return false }
    }
    return true
}
var box1 = Box<Int>()
box1.append(1); box1.append(2); box1.append(3)
var box2 = Box<Int>()
box2.append(1); box2.append(2); box2.append(3)
print("All match: \(allItemsMatch(box1, box2))")

Saída:

TEXT 📖 Somente leitura
All match: true

6. Exemplo Completo: Fila Genérica e Sistema de Cache

SWIFT
// ============================================
// Full example: Generic queue + bounded cache
// Features: general-purpose queue, cache wrapper, statistics
// ============================================
import Foundation
// 1. Generic queue
struct Queue<Element> {
    private var items: [Element] = []
    mutating func enqueue(_ item: Element) { items.append(item) }
    mutating func dequeue() -> Element? { items.isEmpty ? nil : items.removeFirst() }
    var count: Int { items.count }
}
// 2. Generic cache (with capacity limit)
class Cache<Key: Hashable, Value> {
    private var storage: [Key: Value] = [:]
    private let capacity: Int
    init(capacity: Int = 100) {
        self.capacity = capacity
    }
    func set(_ value: Value, for key: Key) {
        if storage.count >= capacity {
            storage.removeFirst()
        }
        storage[key] = value
    }
    func get(for key: Key) -> Value? { storage[key] }
    var count: Int { storage.count }
}
// 3. Usage example
var queue = Queue<String>()
queue.enqueue("Task 1")
queue.enqueue("Task 2")
queue.enqueue("Task 3")
print("Queue count: \(queue.count)")
print("Dequeue: \(queue.dequeue() ?? "")")
let cache = Cache<String, Int>(capacity: 3)
cache.set(42, for: "answer")
cache.set(100, for: "score")
print("Cached answer: \(cache.get(for: "answer") ?? 0)")
print("Cache count: \(cache.count)")

Saída:

TEXT 📖 Somente leitura
Queue count: 3
Dequeue: Task 1
Cached answer: 42
Cache count: 2

❓ Perguntas Frequentes

P: Qual é a diferença entre genéricos e Any? R: Genéricos preservam informações de tipo e são seguros em tempo de compilação. Any verifica os tipos em tempo de execução. Genéricos significam "o compilador conhece o tipo concreto"; Any significa "o compilador desiste da verificação de tipo". P: Quando devo usar parâmetros genéricos vs restrições de protocolo? R: Parâmetros genéricos funcionam para cenários onde "o tipo pode ser arbitrário, mas as operações são consistentes". Restrições de protocolo funcionam para cenários onde "o tipo deve satisfazer uma interface específica". Os dois são frequentemente usados juntos. P: Qual é a diferença entre tipos associados e parâmetros genéricos? R: Tipos associados são definidos dentro de um protocolo e especificados pelo implementador. Parâmetros genéricos são definidos em uma função/tipo e especificados pelo chamador. Tipos associados são essencialmente "os parâmetros genéricos do protocolo". P: Como o Array genérico é implementado? R: O Array da biblioteca padrão é uma struct genérica Array<Element>, onde Element é o parâmetro genérico. Todas as operações de array (append, sort, map) são métodos genéricos. P: Genéricos impactam o desempenho? R: Não. O Swift usa "especialização genérica" -- o compilador gera versões especializadas para cada tipo concreto, então não há sobrecarga em tempo de execução dos genéricos.


📖 Resumo


📝 Exercícios

  1. Básico: Escreva uma função genérica encontrarMax<T: Comparable> que aceita dois parâmetros e retorna o maior. Teste com os tipos Int, Double e String.
  2. Intermediário: Estenda o tipo Pilha da Seção 4 adicionando um método filter que aceita uma closure (Elemento) -> Bool e retorna uma nova pilha filtrada.
  3. Desafio: Use genéricos para implementar um BufferCircular<Elemento> com métodos escrever(_:) e ler() -> Elemento?, capacidade fixa e sobrescrita automática dos dados mais antigos quando cheio.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%