Concorrência Acessível do Swift 6.2
Padrões para adotar o modelo de concorrência do Swift 6.2, onde o código é executado em single-thread por padrão e a concorrência é introduzida explicitamente. Elimina erros comuns de data race sem sacrificar desempenho.
Quando Ativar
- Migrando projetos Swift 5.x ou 6.0/6.1 para Swift 6.2
- Resolvendo erros do compilador relacionados à segurança de data race
- Projetando arquitetura de aplicativo baseada em MainActor
- Offloading de trabalho intensivo de CPU para threads em segundo plano
- Implementando conformances de protocolo em tipos isolados ao MainActor
- Habilitando as configurações de construção Approachable Concurrency no Xcode 26
Problema Central: Offloading Implícito em Segundo Plano
No Swift 6.1 e anteriores, funções async podiam ser implicitamente offloadadas para threads em segundo plano, causando erros de data race mesmo em código aparentemente seguro:
// Swift 6.1: ERRO
@MainActor
final class StickerModel {
let photoProcessor = PhotoProcessor()
func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? {
guard let data = try await item.loadTransferable(type: Data.self) else { return nil }
// Erro: Enviar 'self.photoProcessor' corre o risco de causar data races
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
}
}
O Swift 6.2 corrige isso: funções async permanecem no ator que as chamou por padrão.
// Swift 6.2: OK — async permanece no MainActor, sem data race
@MainActor
final class StickerModel {
let photoProcessor = PhotoProcessor()
func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? {
guard let data = try await item.loadTransferable(type: Data.self) else { return nil }
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
}
}
Padrão Central — Conformances Isoladas
Tipos do MainActor agora podem conformar a protocolos não isolados de forma segura:
protocol Exportable {
func export()
}
// Swift 6.1: ERRO — cruza para o código isolado ao MainActor
// Swift 6.2: OK com conformance isolada
extension StickerModel: @MainActor Exportable {
func export() {
photoProcessor.exportAsPNG()
}
}
O compilador garante que a conformance só é usada no MainActor:
// OK — ImageExporter também é @MainActor
@MainActor
struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // Seguro: mesmo isolamento de ator
}
}
// ERRO — contexto não isolado não pode usar a conformance do MainActor
nonisolated struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // Erro: Conformance isolada ao MainActor não pode ser usada aqui
}
}
Padrão Central — Variáveis Globais e Estáticas
Proteja estado global/estático com MainActor:
// Swift 6.1: ERRO — tipo não-Sendable pode ter estado mutável compartilhado
final class StickerLibrary {
static let shared: StickerLibrary = .init() // Erro
}
// Correção: Anotar com @MainActor
@MainActor
final class StickerLibrary {
static let shared: StickerLibrary = .init() // OK
}
Modo de Inferência Padrão do MainActor
O Swift 6.2 introduz um modo onde o MainActor é inferido por padrão — sem necessidade de anotações manuais:
// Com inferência padrão do MainActor habilitada:
final class StickerLibrary {
static let shared: StickerLibrary = .init() // Implicitamente @MainActor
}
final class StickerModel {
let photoProcessor: PhotoProcessor
var selection: [PhotosPickerItem] // Implicitamente @MainActor
}
extension StickerModel: Exportable { // Conformance implicitamente @MainActor
func export() {
photoProcessor.exportAsPNG()
}
}
Este modo é opt-in e recomendado para aplicativos, scripts e outros alvos executáveis.
Padrão Central — @concurrent para Trabalho em Segundo Plano
Quando você precisa de paralelismo real, faça offloading explicitamente com @concurrent:
Importante: Este exemplo requer as configurações de construção Approachable Concurrency — SE-0466 (isolamento padrão do MainActor) e SE-0461 (NonisolatedNonsendingByDefault). Com essas habilitadas,
extractStickerpermanece no ator que chamou, tornando o acesso a estado mutável seguro. Sem essas configurações, este código tem um data race — o compilador sinalizará.
nonisolated final class PhotoProcessor {
private var cachedStickers: [String: Sticker] = [:]
func extractSticker(data: Data, with id: String) async -> Sticker {
if let sticker = cachedStickers[id] {
return sticker
}
let sticker = await Self.extractSubject(from: data)
cachedStickers[id] = sticker
return sticker
}
// Offload trabalho caro para o pool de threads concorrente
@concurrent
static func extractSubject(from data: Data) async -> Sticker { /* ... */ }
}
// Chamadores devem usar await
let processor = PhotoProcessor()
processedPhotos[item.id] = await processor.extractSticker(data: data, with: item.id)
Para usar @concurrent:
- Marque o tipo contido como
nonisolated - Adicione
@concurrentà função - Adicione
asyncse ainda não for assíncrona - Adicione
awaitnos pontos de chamada
Principais Decisões de Design
| Decisão | Justificativa |
|---|---|
| Single-thread por padrão | O código mais natural é livre de data race; concorrência é opt-in |
| Async permanece no ator chamador | Elimina offloading implícito que causava erros de data race |
| Conformances isoladas | Tipos do MainActor podem conformar a protocolos sem workarounds inseguros |
@concurrent opt-in explícito | Execução em segundo plano é uma escolha deliberada de desempenho, não acidental |
| Inferência padrão do MainActor | Reduz anotações @MainActor desnecessárias para alvos de aplicativo |
| Adoção opt-in | Caminho de migração sem quebra — habilite recursos incrementalmente |
Etapas de Migração
- Habilitar no Xcode: Seção Concurrency do Compilador Swift nas Configurações de Construção
- Habilitar no SPM: Use a API
SwiftSettingsno manifesto do pacote - Usar ferramentas de migração: Mudanças automáticas de código via swift.org/migration
- Comece com padrões do MainActor: Habilite o modo de inferência para alvos de aplicativo
- Adicione
@concurrentonde necessário: Analise primeiro, depois faça offloading de caminhos críticos - Teste minuciosamente: Problemas de data race se tornam erros em tempo de compilação
Melhores Práticas
- Comece no MainActor — escreva código single-thread primeiro, otimize depois
- Use
@concurrentapenas para trabalho intensivo de CPU — processamento de imagens, compressão, computação complexa - Habilite o modo de inferência do MainActor para alvos de aplicativo que são principalmente single-thread
- Analise antes de fazer offloading — use Instruments para encontrar gargalos reais
- Proteja globais com MainActor — estado mutável global/estático precisa de isolamento de ator
- Use conformances isoladas em vez de workarounds
nonisolatedou wrappers@Sendable - Migre incrementalmente — habilite recursos um de cada vez nas configurações de construção
Anti-Padrões a Evitar
- Aplicar
@concurrenta toda funçãoasync(a maioria não precisa de execução em segundo plano) - Usar
nonisolatedpara suprimir erros do compilador sem entender o isolamento - Manter padrões legados
DispatchQueuequando atores fornecem a mesma segurança - Pular verificações
model.availabilityem código de Foundation Models relacionado à concorrência - Lutar contra o compilador — se ele reporta um data race, o código tem um problema de concorrência real
- Assumir que todo código
asyncé executado em segundo plano (Swift 6.2 padrão: permanece no ator chamador)
Quando Usar
- Todos os novos projetos Swift 6.2+ (Concorrência Acessível é o padrão recomendado)
- Migrando aplicativos existentes de Swift 5.x ou 6.0/6.1 concorrência
- Resolvendo erros do compilador relacionados à segurança de data race durante a adoção do Xcode 26
- Construindo arquiteturas de aplicativo centradas no MainActor (a maioria dos aplicativos de UI)
- Otimização de desempenho — offloading de computações pesadas específicas para segundo plano