DEV Community

Cover image for Concorrência Moderna no Swift: async/await e actors explicados
Felipe Carvalho
Felipe Carvalho

Posted on

Concorrência Moderna no Swift: async/await e actors explicados

Olá desenvolvedores! Se você está se aventurando no mundo do Swift e se perguntando como lidar com concorrência de maneira moderna e eficiente, este artigo é para você. Vamos explorar o sistema de concorrência introduzido no Swift 5.5, com foco em async/await e actors, e entender por que essas ferramentas são tão importantes para o desenvolvimento de aplicações robustas.

O problema: Concorrência é complicada

Aplicações modernas precisam fazer várias coisas ao mesmo tempo:

  • Baixar dados da internet enquanto a interface permanece responsiva
  • Processar imagens sem travar a aplicação
  • Lidar com múltiplas requisições de rede simultaneamente
  • Sincronizar dados entre diferentes threads

Antes do Swift 5.5, gerenciar essas operações era um grande desafio, sujeito a diversos problemas:

  • Race conditions: quando duas threads acessam e modificam dados compartilhados simultaneamente
  • Deadlocks: quando threads ficam bloqueadas esperando uma pela outra
  • Callback hell: código aninhado de difícil manutenção e leitura
  • Thread explosion: criar threads demais pode degradar a performance

O mundo antigo: Como fazíamos antes

Grand Central Dispatch (GCD)

O GCD foi por muito tempo a principal ferramenta para concorrência no iOS:

// Executando código em background
DispatchQueue.global().async {
    // Operação demorada
    let resultado = self.operacaoDemorada()

    // Volta para a main thread para atualizar UI
    DispatchQueue.main.async {
        self.atualizarUI(com: resultado)
    }
}
Enter fullscreen mode Exit fullscreen mode

Completion Handlers (Callbacks)

Muito código assíncrono era escrito usando callbacks:

func buscarDados(completion: @escaping (Resultado) -> Void) {
    DispatchQueue.global().async {
        // Busca dados da rede
        let dados = self.downloadDados()

        // Callback com resultado
        DispatchQueue.main.async {
            completion(dados)
        }
    }
}

// Uso (potencial callback hell)
buscarDados { resultado in
    self.processarResultado(resultado) { dadosProcessados in
        self.salvarDados(dadosProcessados) { sucesso in
            if sucesso {
                self.mostrarMensagem("Sucesso!")
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Operation e OperationQueue

Uma abordagem mais estruturada, mas ainda complexa:

let operacao = BlockOperation {
    // Código a ser executado concorrentemente
}

let fila = OperationQueue()
fila.addOperation(operacao)
Enter fullscreen mode Exit fullscreen mode

O novo mundo: Swift Concurrency

Com Swift 5.5, Apple introduziu um sistema de concorrência moderno e seguro que revolucionou como escrevemos código assíncrono.

async/await: Código assíncrono com aparência síncrona

O async/await permite escrever código assíncrono que parece síncrono:

// Função assíncrona
func buscarDados() async throws -> [Dado] {
    // Simula uma operação de rede
    try await Task.sleep(nanoseconds: 1_000_000_000) // 1 segundo
    return [Dado(id: 1), Dado(id: 2)]
}

// Uso em outra função assíncrona
func atualizarDados() async {
    do {
        let dados = try await buscarDados()
        // Não precisa de DispatchQueue.main.async
        atualizarUI(com: dados)
    } catch {
        mostrarErro(error)
    }
}

// Chamada a partir de código síncrono
func botaoAtualizar() {
    Task {
        await atualizarDados()
    }
}
Enter fullscreen mode Exit fullscreen mode

Benefícios do async/await:

  • Legibilidade: código linear e fácil de seguir
  • Tratamento de erros nativo: uso de try/catch em vez de callback errors
  • Simplicidade: menos código e mais segurança
  • Manutenção: código mais fácil de depurar e modificar

Actor: Proteção de estado em ambientes concorrentes

Um actor é um tipo que garante acesso seguro ao seu estado mutável:

// Definição de um actor
actor Contador {
    private var valor = 0

    func incrementar() -> Int {
        valor += 1
        return valor
    }

    func obterValor() -> Int {
        return valor
    }
}

// Uso do actor
func exemplo() async {
    let contador = Contador()

    // Acesso seguro - isolamento automático
    let novoValor = await contador.incrementar()
    print("Novo valor: \(novoValor)")
}
Enter fullscreen mode Exit fullscreen mode

Por que actors são importantes?

  • Segurança de thread: impossibilita race conditions no estado interno
  • Sincronização automática: o compilador garante acesso exclusivo ao estado
  • Simplicidade: sem necessidade de locks, semáforos ou outras primitivas manuais
  • Performance: otimizado pelo sistema runtime

Quando usar cada recurso?

Use async/await quando:

  • Precisar realizar operações que tomam tempo (rede, E/S, processamento pesado)
  • Quiser simplificar código assíncrono com uma sintaxe linear
  • Precisar lidar com operações que podem falhar (com throws)
  • Quiser compor múltiplas operações assíncronas

Use actor quando:

  • Tiver dados mutáveis que precisam ser acessados por múltiplas tarefas
  • Quiser evitar race conditions em state compartilhado
  • Precisar encapsular lógica que requer sincronização
  • Tiver uma classe que precisa ser thread-safe

Exemplo prático: Gerenciador de download de imagens

Vejamos um exemplo completo que combina ambos os conceitos para resolver um problema comum em apps iOS:

// Um actor que gerencia cache de imagens
actor ImageCache {
    private var cache: [URL: UIImage] = [:]

    func image(for url: URL) -> UIImage? {
        return cache[url]
    }

    func saveImage(_ image: UIImage, for url: URL) {
        cache[url] = image
    }

    func clearCache() {
        cache.removeAll()
    }
}

// Serviço de download de imagens
class ImageService {
    private let cache = ImageCache()

    func downloadImage(from url: URL) async throws -> UIImage {
        // Verifica se a imagem já está em cache
        if let cachedImage = await cache.image(for: url) {
            return cachedImage
        }

        // Faz download da imagem
        let (data, _) = try await URLSession.shared.data(from: url)

        guard let image = UIImage(data: data) else {
            throw URLError(.cannotDecodeContentData)
        }

        // Salva no cache
        await cache.saveImage(image, for: url)

        return image
    }
}

// Uso em uma ViewController
class ImageViewController: UIViewController {
    @IBOutlet weak var imageView: UIImageView!
    let imageService = ImageService()

    func loadImage() {
        // Mostra indicador de carregamento
        showLoadingIndicator()

        // Cria uma task para fazer o download
        Task {
            do {
                let url = URL(string: "https://exemplo.com/imagem.jpg")!
                let image = try await imageService.downloadImage(from: url)

                // Atualiza UI na main thread (não precisa de DispatchQueue.main.async)
                imageView.image = image
                hideLoadingIndicator()
            } catch {
                showError(error)
                hideLoadingIndicator()
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Neste exemplo:

  1. ImageCache é um actor que protege o acesso concorrente ao dicionário de cache
  2. ImageService usa async/await para fazer o download e gerenciar o cache
  3. ImageViewController chama o serviço de forma assíncrona com Task

Vantagens do novo sistema de concorrência

  1. Segurança: O compilador verifica o uso correto de await e actor, reduzindo bugs
  2. Legibilidade: Código assíncrono linear e fácil de entender
  3. Manutenção: Menos código para gerenciar threads e sincronização
  4. Performance: O sistema runtime otimiza a execução das tarefas assíncronas
  5. Interoperabilidade: Trabalha bem com código existente baseado em completion handlers

Dicas para migração

Se você está migrando de GCD ou completion handlers para o novo sistema:

  1. Comece identificando funções que usam callbacks e converta-as para async
  2. Use withCheckedContinuation para criar bridges entre código assíncrono antigo e novo
  3. Substitua classes que precisam de thread-safety por actor
  4. Aproveite as funcionalidades da API como TaskGroup para operações paralelas
  5. Migre gradualmente - você pode misturar os dois sistemas durante a transição

Conclusão

O sistema de concorrência moderno do Swift representa um grande avanço na forma como lidamos com operações assíncronas. Com async/await e actors, o código se torna mais seguro, legível e fácil de manter.

Essas ferramentas não apenas resolvem problemas antigos como race conditions e callback hell, mas também abrem portas para novos padrões de design mais robustos e eficientes. Se você ainda não mergulhou nessas funcionalidades, agora é a hora!

E você, já está usando o sistema de concorrência moderno do Swift? Compartilhe suas experiências nos comentários abaixo!


Este artigo foi escrito para desenvolvedores Swift que querem entender melhor as ferramentas modernas de concorrência. Se gostou, não deixe de compartilhar e seguir para mais conteúdo sobre desenvolvimento iOS e Swift, bem como de Flutter e Dart.

Top comments (0)