Introdução à Programação Assíncrona em Swift
\n\nAplicativos móveis modernos raramente dispensam requisições de rede, trabalho com banco de dados ou processamento de arquivos grandes. Todas essas operações exigem tempo e, se executadas na thread principal, a interface do aplicativo "congela", proporcionando uma experiência desagradável ao usuário. É aqui que a programação assíncrona entra em ação.
\n\nAntes do Swift 5.5, os desenvolvedores usavam closures (completion handlers) ou bibliotecas de terceiros (Combine, RxSwift), o que frequentemente levava ao "inferno dos callbacks" e a um código difícil de ler. Com o lançamento do novo modelo Swift Concurrency (async/await), escrever código assíncrono tornou-se tão simples e linear quanto o síncrono.
\n\nNeste guia, abordaremos os conceitos-chave da programação assíncrona em Swift com exemplos práticos. Você aprenderá a usar async/await, Task, Actor e outras ferramentas para criar aplicativos rápidos e responsivos para iOS e macOS.
Conceitos Principais do Swift Concurrency
\n\n1. Async e Await: a base da assincronicidade
\n\nAs palavras-chave async e await são o coração do novo modelo. async declara uma função como assíncrona, e await pausa a execução até obter o resultado, sem bloquear a thread.
Vamos considerar um exemplo simples: carregar dados da rede.
\n\nimport Foundation\n\n// Declara uma função assíncrona\nfunc fetchUserData() async throws -> String {\n let url = URL(string: "https://api.example.com/user/1")!\n let (data, _) = try await URLSession.shared.data(from: url)\n return String(data: data, encoding: .utf8) ?? "Erro"\n}\n\n// Chamada da função assíncrona\nTask {\n do {\n let userData = try await fetchUserData()\n print("Dados do usuário: \\(userData)")\n } catch {\n print("Erro ao carregar: \\(error.localizedDescription)")\n }\n}\n\nObserve: try await significa que estamos aguardando um resultado que pode lançar um erro. O código parece síncrono comum, mas não bloqueia a thread principal.
2. Task: ponto de entrada no mundo assíncrono
\n\nFunções assíncronas só podem ser chamadas de outro contexto assíncrono. Para iniciar uma operação assíncrona a partir de código síncrono (por exemplo, de viewDidLoad()), usa-se Task.
class ViewController: UIViewController {\n \n override func viewDidLoad() {\n super.viewDidLoad()\n \n // Inicia uma tarefa assíncrona\n Task {\n let data = await loadData()\n updateUI(with: data)\n }\n }\n \n func loadData() async -> String {\n // Simula um carregamento longo\n try? await Task.sleep(nanoseconds: 2_000_000_000)\n return "Dados carregados"\n }\n \n func updateUI(with data: String) {\n print("Atualizando UI: \\(data)")\n }\n}\n\nImportante: Task cria uma nova tarefa assíncrona que é executada em uma thread de fundo. Para atualizar a UI, certifique-se de retornar à thread principal — por padrão, await faz isso automaticamente.
3. Actor: proteção contra condição de corrida
\n\nAo trabalhar com multithreading, frequentemente surge o problema de condição de corrida (data race), quando várias threads modificam uma mesma variável simultaneamente. O Swift oferece uma solução elegante — Actor.
Actor é um tipo por referência que garante que apenas uma thread pode acessar seu estado mutável em qualquer momento.
actor Counter {\n private var value = 0\n \n func increment() {\n value += 1\n }\n \n func getValue() -> Int {\n return value\n }\n}\n\n// Uso\nlet counter = Counter