Async/Await em Swift: guia completo com exemplos práticos

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course
Async/Await Swift: métodos, exemplos e guia completo | PythonLib

Async/Await em Swift: guia completo de concorrência estruturada

O desenvolvimento moderno para o ecossistema Apple exige lidar eficientemente com operações assíncronas: baixar dados da rede, manipular arquivos, processar imagens. Antes do async/await, os desenvolvedores usavam closures (completion handlers) ou Combine, o que frequentemente resultava em "callback hell" e gerenciamento complexo de estado. O Swift 5.5 introduziu suporte nativo a funções assíncronas, transformando a forma como escrevemos código concorrente.

Async/Await não é uma biblioteca, mas sim uma construção nativa da linguagem Swift que permite escrever código assíncrono tão simples quanto código síncrono. As palavras-chave async e await tornam o código concorrente linear, legível e seguro. Em vez de closures aninhadas, você escreve chamadas sequenciais, e o compilador gerencia automaticamente as trocas de contexto.

Este artigo é um guia completo sobre async/await em Swift. Você aprenderá como configurar a versão mínima do Swift, dominará os principais métodos (Task, TaskGroup, Actor, async let e outros), verá um exemplo real de aplicativo para download e processamento de imagens, além de receber dicas de performance e evitar erros comuns.

1. O que é e por que usar?

Async/Await é um modelo de concorrência integrado à linguagem Swift a partir da versão 5.5. Ele permite declarar funções como async (assíncronas) e pausar sua execução até que um resultado esteja disponível, usando await. Isso elimina a necessidade de closures complexas e melhora drasticamente a legibilidade do código.

Antes do async/await, o código assíncrono em Swift era escrito com completion handlers, o que gerava aninhamento excessivo e dificultava o tratamento de erros. Com o novo modelo, você pode escrever código que parece síncrono, mas é executado de forma assíncrona, tornando o desenvolvimento mais intuitivo e menos propenso a erros.

Nota: O suporte a async/await está disponível a partir do Swift 5.5, que acompanha o Xcode 13. Certifique-se de que seu projeto esteja configurado para usar pelo menos o iOS 15, macOS 12, tvOS 15 ou watchOS 8.

2. Configuração e requisitos

Para começar a usar async/await no seu projeto Swift, você precisa atender aos seguintes requisitos:

  • Swift 5.5+ — já incluído no Xcode 13 ou superior
  • iOS 15+ / macOS 12+ / tvOS 15+ / watchOS 8+ — versões mínimas dos sistemas operacionais
  • Frameworks — Foundation, UIKit, SwiftUI e outros frameworks da Apple já suportam async/await nativamente

Não é necessário instalar bibliotecas externas. Basta criar um novo projeto no Xcode e começar a usar as palavras-chave async e await.

3. Principais métodos e conceitos

O ecossistema async/await em Swift inclui várias ferramentas poderosas. Vamos explorar as principais:

3.1. Task

Task é a unidade fundamental de trabalho assíncrono. Ela cria um novo contexto de execução que pode ser aguardado com await. Existem dois tipos principais:

  • Task — executa uma operação assíncrona e retorna um resultado
  • Task.detached — cria uma tarefa que não herda o contexto da tarefa pai
func fetchUserData(userId: Int) async throws -> User {
    let url = URL(string: "https://api.exemplo.com/users/\(userId)")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}

// Uso
Task {
    do {
        let user = try await fetchUserData(userId: 42)
        print("Usuário carregado: \(user.name)")
    } catch {
        print("Erro ao carregar usuário: \(error)")
    }
}

3.2. async let

async let permite executar múltiplas tarefas assíncronas em paralelo de forma concisa. É ideal quando você precisa buscar dados independentes simultaneamente.

func loadDashboard() async throws -> Dashboard {
    async let user = fetchUserData(userId: 1)
    async let posts = fetchRecentPosts()
    async let notifications = fetchNotifications()
    
    // Aguarda todas as tarefas em paralelo
    return try await Dashboard(
        user: user,
        posts: posts,
        notifications: notifications
    )
}

3.3. TaskGroup

TaskGroup é útil quando você precisa executar um número dinâmico de tarefas assíncronas e coletar os resultados. É perfeito para processar coleções de dados.

func processImages(urls: [URL]) async throws -> [UIImage] {
    return try await withThrowingTaskGroup(of: UIImage.self) { group in
        for url in urls {
            group.addTask {
                let (data, _) = try await URLSession.shared.data(from: url)
                guard let image = UIImage(data: data) else {
                    throw ImageError.invalidData
                }
                return image
            }
        }
        
        var images: [UIImage] = []
        for try await image in group {
            images.append(image)
        }
        return images
    }
}

3.4. Actor

Actor é um tipo que protege o acesso ao seu estado interno contra condições de corrida (race conditions). Ele é essencial para dados compartilhados em ambientes concorrentes.

actor BankAccount {
    private var balance: Double = 0.0
    
    func deposit(amount: Double) {
        balance += amount
    }
    
    func withdraw(amount: Double) throws {
        guard balance >= amount else {
            throw BankError.insufficientFunds
        }
        balance -= amount
    }
    
    func getBalance() -> Double {
        return balance
    }
}

// Uso seguro com await
let account = BankAccount()
await account.deposit(amount: 1000)
try await account.withdraw(amount: 500)
print(await account.getBalance()) // 500

3.5. AsyncSequence

AsyncSequence permite iterar sobre sequências assíncronas, como streams de dados ou notificações. É útil para processar dados que chegam ao longo do tempo.

for try await line in URLSession.shared.bytes(from: url).lines {
    print("Linha recebida: \(line)")
}

4. Exemplo prático: aplicativo de galeria de imagens

Vamos construir um exemplo completo que baixa múltiplas imagens de uma API, processa-as e as exibe em uma lista. Este exemplo demonstra o uso combinado de TaskGroup, Actor e async/await.

import UIKit

// Modelo de dados
struct Photo: Codable {
    let id: Int
    let url: String
    let title: String
}

// Actor para cache de imagens
actor ImageCache {
    private var cache: [String: UIImage] = [:]
    
    func getImage(forKey key: String) -> UIImage? {
        return cache[key]
    }
    
    func setImage(_ image: UIImage, forKey key: String) {
        cache[key] = image
    }
}

// ViewModel com lógica assíncrona
class GalleryViewModel: ObservableObject {
    @Published var photos: [Photo] = []
    @Published var images: [UIImage] = []
    private let cache = ImageCache()
    
    func loadGallery() async {
        do {
            // 1. Buscar lista de fotos
            let photoURL = URL(string: "https://jsonplaceholder.typicode.com/photos")!
            let (data, _) = try await URLSession.shared.data(from: photoURL)
            photos = try JSONDecoder().decode([Photo].self, from: data)
            
            // 2. Baixar imagens em paralelo com TaskGroup
            try await withThrowingTaskGroup(of: (Int, UIImage).self) { group in
                for (index, photo) in photos.prefix(20).enumerated() {
                    group.addTask {
                        let url = URL(string: photo.url)!
                        let (imageData, _) = try await URLSession.shared.data(from: url)
                        guard let image = UIImage(data: imageData) else {
                            throw ImageError.invalidData
                        }
                        // Armazenar no cache
                        await self.cache.setImage(image, forKey: photo.url)
                        return (index, image)
                    }
                }
                
                // 3. Coletar resultados na ordem correta
                var tempImages = [UIImage?](repeating: nil, count: min(photos.count, 20))
                for try await (index, image) in group {
                    tempImages[index] = image
                }
                images = tempImages.compactMap { $0 }
            }
        } catch {
            print("Erro ao carregar galeria: \(error)")
        }
    }
}

5. Dicas de performance e boas práticas

Dica: Sempre use Task com await dentro de funções marcadas como async. Evite criar tarefas soltas sem gerenciamento adequado.
  • Prefira TaskGroup a async let quando o número de tarefas for dinâmico ou desconhecido em tempo de compilação
  • Use Actor para proteger dados compartilhados entre tarefas concorrentes
  • Evite bloqueios — nunca chame código síncrono pesado dentro de funções async
  • Trate erros adequadamente com try/catch em todas as chamadas async que podem falhar
  • Mantenha a hierarquia de tarefas — tarefas filhas devem ser canceladas automaticamente quando a tarefa pai é cancelada

6. Erros comuns e como evitá-los

Atenção: Um erro comum é esquecer de marcar a função como async ao usar await. O compilador do Swift exige que toda função que contém await seja declarada como async.
Erro Comum Solução
Esquecer await em chamadas async Sempre use await antes de chamar funções marcadas como async
Chamar código async fora de um contexto async Use Task { } para criar um novo contexto assíncrono
Não tratar erros em tarefas Use do/catch ou try? para capturar exceções
Criar data races com variáveis compartilhadas Use actor ou @MainActor para proteger o acesso

7. Conclusão

O async/await revolucionou a forma como escrevemos código concorrente em Swift. Com ele, você pode criar aplicativos mais responsivos, legíveis e seguros

Recommendations