Concorrência e Multithreading em Swift: Nível Avançado
Concorrência (concurrency) é um dos tópicos mais complexos e importantes no desenvolvimento iOS/macOS. Em nível avançado, o Swift oferece ferramentas poderosas: desde o clássico Grand Central Dispatch (GCD) até o modelo moderno async/await e o sistema rigoroso de Actors. Neste artigo, vamos analisar como gerenciar threads de forma eficiente, evitar condições de corrida e escrever código thread-safe.
1. Grand Central Dispatch (GCD): Trabalhando com Filas e Grupos
O GCD continua sendo a base do multithreading em Swift. No entanto, em nível avançado, é importante entender não apenas a diferença entre main e global(), mas também saber gerenciar dependências de tarefas.
Criando Filas Próprias
let serialQueue = DispatchQueue(label: "com.pythonlib.serial")let concurrentQueue = DispatchQueue(label: "com.pythonlib.concurrent", attributes: .concurrent)
serialQueue.async { print("Tarefa 1 na fila serial")}concurrentQueue.async { print("Tarefa 2 na fila concorrente")}DispatchGroup: Sincronizando Múltiplas Tarefas
let group = DispatchGroup()let queue = DispatchQueue.global()
queue.async(group: group) { sleep(1) print("Carregando dados 1")}queue.async(group: group) { sleep(2) print("Carregando dados 2")}
group.notify(queue: DispatchQueue.main) { print("Todos os carregamentos concluídos, atualizando UI")}Use DispatchGroup quando precisar aguardar a conclusão de várias operações paralelas (por exemplo, carregar imagens ou fazer parsing de JSON).
2. Modelo Moderno async/await e Task
A partir do Swift 5.5, a linguagem recebeu suporte nativo para assincronia através das palavras-chave async e await. Isso simplifica a leitura do código e elimina a "pirâmide de closures".
Declarando e Chamando uma Função Assíncrona
func fetchUserData() async -> String { // Simulação de requisição de rede try? await Task.sleep(nanoseconds: 2_000_000_000) return "User data loaded"}
Task { let result = await fetchUserData() print(result) // Exibirá "User data loaded"}Chamadas Paralelas com async let
func loadImage() async -> Data { return Data() }func loadMetadata() async -> String { return "meta" }
Task { async let image = loadImage() async let metadata = loadMetadata() let result = try await (image, metadata) print("Imagem: \\(result.0), metadados: \\(result.1)")}async let permite iniciar várias tarefas assíncronas em paralelo e aguardar todas elas. Isso é mais eficiente do que chamadas sequenciais.
3. Actors: Thread-Safety em Nível de Linguagem
Actors são um novo mecanismo de proteção contra condições de corrida. Eles garantem que o acesso ao seu estado seja sincronizado automaticamente.
Definição e Uso de um Actor
actor BankAccount { private var balance: Double = 0.0 func deposit(amount: Double) { balance += amount print("Saldo após depósito: \\(balance)") } func withdraw(amount: Double) -> Bool { guard balance >= amount else { return false } balance -= amount return true } func getBalance() -> Double { balance }}
Task { let account = BankAccount() await account.deposit(amount: 100) let success = await account.withdraw(amount: 50) print("Saque bem-sucedido: \\(success)")}Todos os métodos de um actor são isolados por padrão — só podem ser chamados através de await. Isso elimina a alteração acidental de dados por múltiplas threads.
Métodos Não Isolados (nonisolated)
actor Logger { nonisolated func log(message: String) { print("Log: \\(message)") }}