Introdução à programação assíncrona em Swift
Aplicativos móveis e de desktop modernos em Swift (iOS/macOS) cada vez mais exigem a execução de operações longas — carregamento de dados da rede, trabalho com arquivos, processamento de imagens — sem bloquear a interface do usuário. Até recentemente, os desenvolvedores usavam closures (completion handlers) e Grand Central Dispatch (GCD), o que levava ao chamado "inferno de callbacks" e dificultava a leitura do código. Com o lançamento do Swift 5.5, a Apple apresentou um modelo fundamentalmente novo de programação assíncrona, baseado nas palavras-chave async e await, bem como em atores e estruturas Task. Neste guia, abordaremos todos os conceitos-chave com exemplos práticos.
1. Fundamentos de async/await em Swift
A palavra-chave async indica que uma função ou método pode pausar sua execução para aguardar o resultado de uma operação assíncrona. A chamada dessa função é marcada com a palavra await. Isso permite escrever código assíncrono de forma tão linear quanto o código síncrono.
// Declarando uma função assíncronafunc fetchUserData(from url: URL) async throws -> Data { let (data, _) = try await URLSession.shared.data(from: url) return data}
// Usando-a no contexto de uma taskTask { do { let url = URL(string: "https://api.example.com/user")! let userData = try await fetchUserData(from: url) print("Dados recebidos: \\(userData.count) bytes") } catch { print("Erro ao carregar: \\(error.localizedDescription)") }}Observe: a chamada await pausa a tarefa atual, mas não bloqueia a thread. O próprio sistema alterna o contexto quando o resultado estiver pronto, tornando o código eficiente e seguro.
2. Atores (Actors) — trabalho seguro com estado
A assincronicidade adiciona o problema de data race. Para resolvê-lo, o Swift introduziu os atores. Um ator é um tipo de referência que isola seu estado e garante que apenas uma tarefa possa acessar suas propriedades mutáveis a qualquer momento.
actor Counter { private var value = 0 func increment() { value += 1 } func getValue() -> Int { return value }}
// Usando o atorlet counter = Counter()
Task { await counter.increment() let currentValue = await counter.getValue() print("Valor atual: \\(currentValue)")}A palavra-chave actor torna automaticamente todos os métodos e propriedades (exceto os imutáveis) assíncronos para acesso externo. Isso evita bloqueios e deadlocks, comuns no gerenciamento manual de locks (NSLock, DispatchSemaphore).
3. TaskGroup e execução paralela de tarefas
Quando é necessário executar várias operações assíncronas independentes em paralelo, use TaskGroup. É uma estrutura que permite iniciar tarefas filhas e coletar seus resultados.
func fetchMultipleUsers(ids: [Int]) async throws -> [User] { try await withThrowingTaskGroup(of: User.self) { group in var users: [User] = [] for id in ids { group.addTask { let url = URL(string: "https://api.example.com/users/\\(id)")! let data = try await URLSession.shared.data(from: url) return try JSONDecoder().decode(User.self, from: data.0) } } for try await user in group { users.append(user) } return users }}
// UsoTask { let users = try? await fetchMultipleUsers(ids: [1, 2, 3, 4, 5]) print("Usuários carregados: \\(users?.count ?? 0)")}O método withThrowi