Concorrência e Multithreading em Swift: Nível Avançado
\n\nAplicativos modernos para iOS e macOS exigem alta performance e capacidade de resposta da interface. O trabalho avançado com concorrência em Swift vai muito além do uso simples do Grand Central Dispatch (GCD). Neste artigo, exploraremos as principais ferramentas do Swift 5.5+ para gerenciar tarefas assíncronas: async/await, Actors, TaskGroup e o protocolo Sendable. Você aprenderá como evitar condições de corrida, estruturar corretamente o código multithread e otimizar a performance.
1. Fundamentos de async/await e Concorrência Estruturada
\n\nAs palavras-chave async e await são a base da concorrência moderna em Swift. Elas permitem escrever código assíncrono que se parece com código síncrono, simplificando bastante a leitura e a depuração.
Declaração de uma função assíncrona:
\nfunc fetchUserData() async throws -> User {\n let url = URL(string: "https://api.example.com/user")!\n let (data, _) = try await URLSession.shared.data(from: url)\n let decoder = JSONDecoder()\n return try decoder.decode(User.self, from: data)\n}\n\nChamada de uma função assíncrona com tratamento de erros:
\nTask {\n do {\n let user = try await fetchUserData()\n print("User name: \\(user.name)")\n } catch {\n print("Failed to fetch user: \\(error)")\n }\n}\n\nPontos importantes:
\n- \n
- Concorrência Estruturada: Cada tarefa assíncrona tem um tempo de vida claro, limitado pelo escopo da
Taskpai. \n - Cancelamento de tarefas: Cancelamento automático de tarefas filhas quando a Task pai é cancelada. \n
- Prioridades: Use
Task(priority: .userInitiated)para tarefas importantes para o usuário. \n
2. Actors: proteção contra condições de corrida
\n\nAntes do Swift 5.5, os desenvolvedores frequentemente usavam filas serializadas do GCD para proteger o estado compartilhado. Os Actors fornecem isolamento embutido: o acesso ao estado de um actor só é possível através de await, garantindo segurança de thread em nível de compilador.
Exemplo de Actor para uma conta bancária:
\nactor BankAccount {\n private var balance: Double = 0.0\n\n func deposit(amount: Double) {\n balance += amount\n }\n\n func withdraw(amount: Double) throws {\n guard balance >= amount else {\n throw BankError.insufficientFunds\n }\n balance -= amount\n }\n\n func getBalance() -> Double {\n return balance\n }\n}\n\n// Uso\nlet account = BankAccount()\nTask {\n await account.deposit(amount: 100.0)\n let currentBalance = await account.getBalance()\n print("Balance: \\(currentBalance)")\n}\n\nVantagens dos Actors:
\n- \n
- Verificação em tempo de compilação: Tentar acessar uma propriedade de um actor sem
awaitcausará um erro de compilação. \n - Reentrância (Reentrancy): Um actor pode pausar a execução do método atual (por exemplo, durante um
awaitdentro do actor) e processar outra chamada, prevenindo deadlocks. \n - Performance: Actors usam mecanismos de sincronização otimizados, geralmente mais rápidos que bloqueios manuais. \n
3. TaskGroup: processamento paralelo de dados
\n\nQuando você precisa executar várias operações assíncronas independentes e aguardar sua conclusão, o TaskGroup é a ferramenta ideal. Ele gerencia automaticamente o número de tarefas paralelas e coleta os resultados.
Exemplo de download paralelo de várias imagens:
\nfunc downloadImages(urls: [URL])