Swift: Closures do Swift
Closures são blocos autônomos de funcionalidade no Swift que podem capturar e armazenar referências a variáveis e constantes do seu contexto circundante. Esta lição começa com expressões de closure e avança até dominar um dos recursos de linguagem mais flexíveis do Swift.
1. O Que Você Vai Aprender
- Sintaxe de expressão de closure
- Regras de closure à direita
- Nomes abreviados de parâmetro
$0,$1, etc. - Mecanismo de captura de valor
- Regras de ordenação personalizadas com closures
2. Uma História Real de uma Analista de Dados
(1) Problema: Lógica de Ordenação Não Reutilizável e Código Verboso
Alice precisa ordenar dados de usuário — por nome, por idade, por data de cadastro. Sua abordagem inicial é escrever uma função separada para cada ordenação:
func sortByName(_ a: [String: Any], _ b: [String: Any]) -> Bool {
return (a["name"] as! String) < (b["name"] as! String)
}
func sortByAge(_ a: [String: Any], _ b: [String: Any]) -> Bool {
return (a["age"] as! Int) < (b["age"] as! Int)
}
Toda vez que a regra de ordenação muda, ela precisa escrever uma nova função, causando inchaço no código. A lógica de ordenação está espalhada por múltiplas funções — você não pode especificá-la dinamicamente no local da chamada.
(2) A Solução com Closures
As closures do Swift permitem embutir a regra de ordenação diretamente no local da chamada sorted(by:):
let sortedByAge = users.sorted { $0["age"] as! Int < $1["age"] as! Int }
let sortedByName = users.sorted { $0["name"] as! String < $1["name"] as! String }
A lógica de ordenação passa de "escrever uma função → passá-la" para "defini-la onde você a usa".
(3) Benefício: Definição Embutida, Troca Flexível
| Dimensão | Função Isolada | Closure Embutida |
|---|---|---|
| Linhas de Código | 3-5 linhas por ordenação | 1 linha |
| Legibilidade | Precisa encontrar a definição da função | Lógica visível no local da chamada |
| Flexibilidade | Todas as estratégias devem ser pré-definidas | Definida na hora |
| Acesso ao Contexto | Não pode capturar variáveis externas | Pode capturar estado contextual |
3. Expressões de Closure
(1) Sintaxe Básica
Uma expressão de closure é uma maneira concisa de escrever uma closure. A sintaxe completa é:
graph LR
A[Expressão de Closure] --> B["{ (parâmetros) -> TipoRetorno in"]
A --> C[" instruções"]
A --> D["}"]
B --> E["{ (a: Int, b: Int) -> Bool in"]
C --> F[" return a < b"]
D --> G["}"]
| Componente | Obrigatório | Observações |
|---|---|---|
{ } |
Sim | Closures são envolvidas por chaves |
| Lista de parâmetros | Não | Pode ser omitida usando nomes abreviados |
-> TipoRetorno |
Não | Pode ser inferido automaticamente |
in |
Condicional | Obrigatório quando parâmetros ou tipo de retorno são declarados |
▶ Exemplo: Três Maneiras de Escrever Expressões de Closure
// ============================================
// Three ways to write closures for sorted(by:)
// ============================================
let numbers = [5, 2, 8, 1, 9]
// Approach 1: Full closure expression
let sorted1 = numbers.sorted(by: { (a: Int, b: Int) -> Bool in
return a < b
})
// Approach 2: Type inference (omit parameter types and return type)
let sorted2 = numbers.sorted(by: { a, b in return a < b })
// Approach 3: Omit return (single-expression implicit return)
let sorted3 = numbers.sorted(by: { a, b in a < b })
print("sorted3: \(sorted3)")
Saída:
TEXT 📖 Somente leiturasorted3: [1, 2, 5, 8, 9]
4. Closures à Direita e Nomes Abreviados de Parâmetro
(1) Closures à Direita
Quando uma closure é o último argumento de uma função, você pode movê-la para fora dos parênteses:
| Estilo | Exemplo |
|---|---|
| Padrão | sorted(by: { a, b in a < b }) |
| À direita | sorted { a, b in a < b } |
| Multi-parâmetro à direita | animate(duration: 1) { ... } |
▶ Exemplo: Simplificando Chamadas com Closures à Direita
// ============================================
// Trailing closure concise syntax
// ============================================
let scores = [78, 92, 85, 68, 99]
// Standard syntax
let ascending1 = scores.sorted(by: { a, b in a < b })
// Trailing closure syntax
let ascending2 = scores.sorted { a, b in a < b }
print("Ascending: \(ascending2)")
// Multiple closure parameters also supported
func performRequest(url: String, onSuccess: (String) -> Void, onError: (Error) -> Void) {
// Simulate network request
onSuccess("Data received")
}
performRequest(url: "https://api.example.com") { data in
print("Success: \(data)")
} onError: { error in
print("Error: \(error)")
}
Saída:
TEXT 📖 Somente leituraAscending: [68, 78, 85, 92, 99] Success: Data received
(2) Nomes Abreviados de Parâmetro
O Swift fornece automaticamente nomes abreviados para parâmetros de closure inline: $0, $1, $2, ...
| Sintaxe Completa | Abreviada |
|---|---|
{ a, b in a < b } |
{ $0 < $1 } |
{ nome in print(nome) } |
{ print($0) } |
{ a, b, c in a + b + c } |
{ $0 + $1 + $2 } |
▶ Exemplo: Nomes Abreviados de Parâmetro em Ação
// ============================================
// Step-by-step simplification with shorthand names
// ============================================
let words = ["banana", "apple", "cherry", "date"]
// Full syntax
let sortedA = words.sorted(by: { (a: String, b: String) -> Bool in
return a < b
})
// Shorthand parameter names — most concise
let sortedB = words.sorted(by: { $0 < $1 })
// Even simpler — pass the operator function directly
let sortedC = words.sorted(by: <)
print(sortedC)
Saída:
TEXT 📖 Somente leitura["apple", "banana", "cherry", "date"]Armadilha Comum: Ao usar nomes abreviados de parâmetro, você pode omitir
returnse o corpo da closure contiver apenas uma única expressão. Se houver múltiplas instruções, você deve escreverreturn.
5. Captura de Valor
Uma closure pode capturar constantes e variáveis do seu contexto circundante, mesmo depois que esse contexto não existe mais.
graph TB
A[Função externa criarIncrementador] --> B["Declara variável total = 0"]
A --> C["Retorna closure { total += quantidade; return total }"]
C --> D[Closure captura referência para total]
D --> E[Mesmo após criarIncrementador retornar]
E --> F["total permanece vivo dentro da closure"]
▶ Exemplo: Closure Capturando Variáveis de Contexto
// ============================================
// Value capture — generating an accumulator
// ============================================
func makeIncrementer(step: Int) -> () -> Int {
var total = 0
let incrementer: () -> Int = {
total += step // Captures total and step
return total
}
return incrementer
}
let incrementByTwo = makeIncrementer(step: 2)
print(incrementByTwo()) // 2
print(incrementByTwo()) // 4
print(incrementByTwo()) // 6
Saída:
TEXT 📖 Somente leitura2 4 6Dica: A captura de valor é um dos recursos mais poderosos das closures. O Swift gerencia automaticamente a memória das variáveis capturadas — uma closure mantém uma referência forte às variáveis capturadas até que a própria closure seja liberada.
▶ Exemplo: Regras de Ordenação Personalizadas
// ============================================
// Custom sorting rules with closures
// ============================================
struct Product {
let name: String
let price: Double
let rating: Int
}
let products = [
Product(name: "Laptop", price: 1299, rating: 5),
Product(name: "Mouse", price: 29, rating: 4),
Product(name: "Keyboard", price: 99, rating: 5),
Product(name: "Monitor", price: 399, rating: 3)
]
// Sort by price ascending
let byPrice = products.sorted { $0.price < $1.price }
print("Cheapest: \(byPrice.first!.name)")
// Sort by rating descending, then by price ascending on tie
let byRatingThenPrice = products.sorted {
if $0.rating != $1.rating {
return $0.rating > $1.rating
}
return $0.price < $1.price
}
print("Best value: \(byRatingThenPrice.first!.name)")
Saída:
TEXT 📖 Somente leituraCheapest: Mouse Best value: Keyboard
6. Exemplo Completo: Ordenador de Lista de Usuários
// ============================================
// Complete example: User list sorter
// Features: Closure expressions + trailing closures + shorthand names + value capture
// ============================================
import Foundation
// 1. User model
struct User {
let name: String
let age: Int
let score: Double
}
let users = [
User(name: "Alice", age: 28, score: 92.5),
User(name: "Bob", age: 22, score: 85.0),
User(name: "Charlie", age: 35, score: 95.5),
User(name: "Diana", age: 19, score: 78.0)
]
// 2. Sorter factory — captures sort direction
func makeSorter(ascending: Bool) -> (User, User) -> Bool {
return { ascending ? $0.score < $1.score : $0.score > $1.score }
}
// 3. Sort using shorthand parameter names
let ascendingSorter = makeSorter(ascending: true)
let sortedAsc = users.sorted(by: ascendingSorter)
print("Ascending:")
for u in sortedAsc {
print(" \(u.name): \(u.score)")
}
// 4. Inline sort with trailing closure
let sortedDesc = users.sorted { $0.score > $1.score }
print("Descending:")
for u in sortedDesc {
print(" \(u.name): \(u.score)")
}
Saída:
TEXT 📖 Somente leituraAscending: Diana: 78.0 Bob: 85.0 Alice: 92.5 Charlie: 95.5 Descending: Charlie: 95.5 Alice: 92.5 Bob: 85.0 Diana: 78.0
❓ Perguntas Frequentes
P: Qual é a diferença entre uma closure e uma função? R: Closures são funções anônimas com uma sintaxe mais concisa. Funções têm nomes e podem ser chamadas repetidamente; closures são tipicamente usadas para cenários únicos. Closures podem capturar variáveis contextuais — funções aninhadas também podem, mas funções globais comuns não. P: Quando devo obrigatoriamente escrever a palavra-chave
in? R: Quando uma expressão de closure declara uma lista de parâmetros ou tipo de retorno, você deve usarinpara separar a declaração do corpo. Se você usar nomes abreviados de parâmetro (como$0) e omitir todas as anotações de tipo,innão é necessário. P: Como a ordem de$0e$1é determinada? R:$0é o primeiro parâmetro da closure,$1é o segundo, e assim por diante. A ordem é determinada pela ordem em que os argumentos são passados quando a closure é chamada, não pelos nomes dos parâmetros. P: Os valores capturados são cópias ou referências? R: Closures capturam variáveis por referência (semelhante a inout) — quando múltiplas closures compartilham a mesma variável, alterações em uma afetam a outra. Constantes são capturadas por cópia de valor. P: E se uma closure for muito longa e prejudicar a legibilidade? R: Se o corpo da closure exceder 5-10 linhas, extraia-o para uma função isolada ou uma variável de closure nomeada antes de passá-lo. Por exemplo:let ordenador = { a, b in ... }; sorted(by: ordenador).
📖 Resumo
- Expressões de closure são envolvidas por
{ }, com parâmetros e tipo de retorno declarados antes da palavra-chavein - Closures à direita permitem omitir parênteses para o último argumento, tornando as chamadas mais naturais
- Nomes abreviados de parâmetro
$0,$1,$2simplificam muito a escrita de closures - Closures de expressão única podem omitir a palavra-chave
return(retorno implícito) - Closures podem capturar e modificar variáveis e constantes do seu contexto circundante
sorted(by:)é um dos casos de uso mais comuns de closures
📝 Exercícios
- Básico: Dado o array de inteiros
[3, 7, 1, 9, 4, 6], escrevasorted(by:)de três maneiras diferentes (closure completa, inferência de tipo, nomes abreviados de parâmetro) para ordenar em ordem descendente. - Intermediário: Escreva uma função
repetirTarefa(vezes:tarefa:)que recebe uma contagem e uma closure, executando a closure o número especificado de vezes. Em seguida, chame-a para imprimir "Olá, Swift!" 5 vezes. - Desafio: Implemente uma função
criarCalculadoraque recebe uma string de operador (+,-,*,/) e retorna a closure correspondente(Int, Int) -> Int. Em seguida, use a função para criar uma closure de adição e uma closure de multiplicação, calculando cada uma para(10, 5).