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)
}
}
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!")
}
}
}
}
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)
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()
}
}
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)")
}
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()
}
}
}
}
Neste exemplo:
- ImageCache é um actor que protege o acesso concorrente ao dicionário de cache
- ImageService usa async/await para fazer o download e gerenciar o cache
- ImageViewController chama o serviço de forma assíncrona com Task
Vantagens do novo sistema de concorrência
-
Segurança: O compilador verifica o uso correto de
await
eactor
, reduzindo bugs - Legibilidade: Código assíncrono linear e fácil de entender
- Manutenção: Menos código para gerenciar threads e sincronização
- Performance: O sistema runtime otimiza a execução das tarefas assíncronas
- 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:
- Comece identificando funções que usam callbacks e converta-as para
async
- Use
withCheckedContinuation
para criar bridges entre código assíncrono antigo e novo - Substitua classes que precisam de thread-safety por
actor
- Aproveite as funcionalidades da API como
TaskGroup
para operações paralelas - 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)