Async/Await em Swift: guia completo de concorrência estruturada
O desenvolvimento moderno para o ecossistema Apple exige lidar eficientemente com operações assíncronas: baixar dados da rede, manipular arquivos, processar imagens. Antes do async/await, os desenvolvedores usavam closures (completion handlers) ou Combine, o que frequentemente resultava em "callback hell" e gerenciamento complexo de estado. O Swift 5.5 introduziu suporte nativo a funções assíncronas, transformando a forma como escrevemos código concorrente.
Async/Await não é uma biblioteca, mas sim uma construção nativa da linguagem Swift que permite escrever código assíncrono tão simples quanto código síncrono. As palavras-chave async e await tornam o código concorrente linear, legível e seguro. Em vez de closures aninhadas, você escreve chamadas sequenciais, e o compilador gerencia automaticamente as trocas de contexto.
Este artigo é um guia completo sobre async/await em Swift. Você aprenderá como configurar a versão mínima do Swift, dominará os principais métodos (Task, TaskGroup, Actor, async let e outros), verá um exemplo real de aplicativo para download e processamento de imagens, além de receber dicas de performance e evitar erros comuns.
1. O que é e por que usar?
Async/Await é um modelo de concorrência integrado à linguagem Swift a partir da versão 5.5. Ele permite declarar funções como async (assíncronas) e pausar sua execução até que um resultado esteja disponível, usando await. Isso elimina a necessidade de closures complexas e melhora drasticamente a legibilidade do código.
Antes do async/await, o código assíncrono em Swift era escrito com completion handlers, o que gerava aninhamento excessivo e dificultava o tratamento de erros. Com o novo modelo, você pode escrever código que parece síncrono, mas é executado de forma assíncrona, tornando o desenvolvimento mais intuitivo e menos propenso a erros.
2. Configuração e requisitos
Para começar a usar async/await no seu projeto Swift, você precisa atender aos seguintes requisitos:
- Swift 5.5+ — já incluído no Xcode 13 ou superior
- iOS 15+ / macOS 12+ / tvOS 15+ / watchOS 8+ — versões mínimas dos sistemas operacionais
- Frameworks — Foundation, UIKit, SwiftUI e outros frameworks da Apple já suportam async/await nativamente
Não é necessário instalar bibliotecas externas. Basta criar um novo projeto no Xcode e começar a usar as palavras-chave async e await.
3. Principais métodos e conceitos
O ecossistema async/await em Swift inclui várias ferramentas poderosas. Vamos explorar as principais:
3.1. Task
Task é a unidade fundamental de trabalho assíncrono. Ela cria um novo contexto de execução que pode ser aguardado com await. Existem dois tipos principais:
- Task — executa uma operação assíncrona e retorna um resultado
- Task.detached — cria uma tarefa que não herda o contexto da tarefa pai
func fetchUserData(userId: Int) async throws -> User {
let url = URL(string: "https://api.exemplo.com/users/\(userId)")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(User.self, from: data)
}
// Uso
Task {
do {
let user = try await fetchUserData(userId: 42)
print("Usuário carregado: \(user.name)")
} catch {
print("Erro ao carregar usuário: \(error)")
}
}
3.2. async let
async let permite executar múltiplas tarefas assíncronas em paralelo de forma concisa. É ideal quando você precisa buscar dados independentes simultaneamente.
func loadDashboard() async throws -> Dashboard {
async let user = fetchUserData(userId: 1)
async let posts = fetchRecentPosts()
async let notifications = fetchNotifications()
// Aguarda todas as tarefas em paralelo
return try await Dashboard(
user: user,
posts: posts,
notifications: notifications
)
}
3.3. TaskGroup
TaskGroup é útil quando você precisa executar um número dinâmico de tarefas assíncronas e coletar os resultados. É perfeito para processar coleções de dados.
func processImages(urls: [URL]) async throws -> [UIImage] {
return try await withThrowingTaskGroup(of: UIImage.self) { group in
for url in urls {
group.addTask {
let (data, _) = try await URLSession.shared.data(from: url)
guard let image = UIImage(data: data) else {
throw ImageError.invalidData
}
return image
}
}
var images: [UIImage] = []
for try await image in group {
images.append(image)
}
return images
}
}
3.4. Actor
Actor é um tipo que protege o acesso ao seu estado interno contra condições de corrida (race conditions). Ele é essencial para dados compartilhados em ambientes concorrentes.
actor BankAccount {
private var balance: Double = 0.0
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) throws {
guard balance >= amount else {
throw BankError.insufficientFunds
}
balance -= amount
}
func getBalance() -> Double {
return balance
}
}
// Uso seguro com await
let account = BankAccount()
await account.deposit(amount: 1000)
try await account.withdraw(amount: 500)
print(await account.getBalance()) // 500
3.5. AsyncSequence
AsyncSequence permite iterar sobre sequências assíncronas, como streams de dados ou notificações. É útil para processar dados que chegam ao longo do tempo.
for try await line in URLSession.shared.bytes(from: url).lines {
print("Linha recebida: \(line)")
}
4. Exemplo prático: aplicativo de galeria de imagens
Vamos construir um exemplo completo que baixa múltiplas imagens de uma API, processa-as e as exibe em uma lista. Este exemplo demonstra o uso combinado de TaskGroup, Actor e async/await.
import UIKit
// Modelo de dados
struct Photo: Codable {
let id: Int
let url: String
let title: String
}
// Actor para cache de imagens
actor ImageCache {
private var cache: [String: UIImage] = [:]
func getImage(forKey key: String) -> UIImage? {
return cache[key]
}
func setImage(_ image: UIImage, forKey key: String) {
cache[key] = image
}
}
// ViewModel com lógica assíncrona
class GalleryViewModel: ObservableObject {
@Published var photos: [Photo] = []
@Published var images: [UIImage] = []
private let cache = ImageCache()
func loadGallery() async {
do {
// 1. Buscar lista de fotos
let photoURL = URL(string: "https://jsonplaceholder.typicode.com/photos")!
let (data, _) = try await URLSession.shared.data(from: photoURL)
photos = try JSONDecoder().decode([Photo].self, from: data)
// 2. Baixar imagens em paralelo com TaskGroup
try await withThrowingTaskGroup(of: (Int, UIImage).self) { group in
for (index, photo) in photos.prefix(20).enumerated() {
group.addTask {
let url = URL(string: photo.url)!
let (imageData, _) = try await URLSession.shared.data(from: url)
guard let image = UIImage(data: imageData) else {
throw ImageError.invalidData
}
// Armazenar no cache
await self.cache.setImage(image, forKey: photo.url)
return (index, image)
}
}
// 3. Coletar resultados na ordem correta
var tempImages = [UIImage?](repeating: nil, count: min(photos.count, 20))
for try await (index, image) in group {
tempImages[index] = image
}
images = tempImages.compactMap { $0 }
}
} catch {
print("Erro ao carregar galeria: \(error)")
}
}
}
5. Dicas de performance e boas práticas
Task com await dentro de funções marcadas como async. Evite criar tarefas soltas sem gerenciamento adequado.
- Prefira TaskGroup a async let quando o número de tarefas for dinâmico ou desconhecido em tempo de compilação
- Use Actor para proteger dados compartilhados entre tarefas concorrentes
- Evite bloqueios — nunca chame código síncrono pesado dentro de funções async
- Trate erros adequadamente com
try/catchem todas as chamadas async que podem falhar - Mantenha a hierarquia de tarefas — tarefas filhas devem ser canceladas automaticamente quando a tarefa pai é cancelada
6. Erros comuns e como evitá-los
async ao usar await. O compilador do Swift exige que toda função que contém await seja declarada como async.
| Erro Comum | Solução |
|---|---|
Esquecer await em chamadas async |
Sempre use await antes de chamar funções marcadas como async |
| Chamar código async fora de um contexto async | Use Task { } para criar um novo contexto assíncrono |
| Não tratar erros em tarefas | Use do/catch ou try? para capturar exceções |
| Criar data races com variáveis compartilhadas | Use actor ou @MainActor para proteger o acesso |
7. Conclusão
O async/await revolucionou a forma como escrevemos código concorrente em Swift. Com ele, você pode criar aplicativos mais responsivos, legíveis e seguros