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 contra data races
- Projetando arquitetura de aplicativos baseada em MainActor
- Descarregando trabalho intensivo de CPU para threads em segundo plano
- Implementando conformidades de protocolo em tipos isolados por MainActor
- Habilitando as configurações de build "Approachable Concurrency" no Xcode 26
Problema Central: Descarregamento Implícito em Segundo Plano
No Swift 6.1 e anteriores, funções async podiam ser implicitamente descarregadas 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 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 actor chamador 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 — Conformidades Isoladas
Tipos MainActor agora podem estar em conformidade com protocolos não isolados de forma segura:
protocol Exportable {
func export()
}
// Swift 6.1: ERRO — cruza para código isolado pelo MainActor
// Swift 6.2: OK com conformidade isolada
extension StickerModel: @MainActor Exportable {
func export() {
photoProcessor.exportAsPNG()
}
}
O compilador garante que a conformidade seja usada apenas 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 actor
}
}
// ERRO — contexto não isolado não pode usar conformidade do MainActor
nonisolated struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // Erro: conformidade isolada pelo MainActor não pode ser usada aqui
}
}
Padrão Central — Variáveis Globais e Estáticas
Proteja o 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 ativada:
final class StickerLibrary {
static let shared: StickerLibrary = .init() // Implicitamente @MainActor
}
final class StickerModel {
let photoProcessor: PhotoProcessor
var selection: [PhotosPickerItem] // Implicitamente @MainActor
}
extension StickerModel: Exportable { // Conformidade implicitamente @MainActor
func export() {
photoProcessor.exportAsPNG()
}
}
Este modo é opcional e recomendado para aplicativos, scripts e outros targets executáveis.
Padrão Central — @concurrent para Trabalho em Segundo Plano
Quando você precisa de paralelismo real, descarregue explicitamente com @concurrent:
Importante: Este exemplo requer as configurações de build "Approachable Concurrency" — SE-0466 (isolamento padrão do MainActor) e SE-0461 (NonisolatedNonsendingByDefault). Com elas ativadas,
extractStickerpermanece no actor do chamador, 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
}
// Descarregar trabalho pesado para o pool de threads concorrentes
@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 que o contém 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-threaded por padrão | O código mais natural é livre de data races; concorrência é opt-in |
| Async permanece no actor chamador | Elimina o descarregamento implícito que causava erros de data race |
| Conformidades isoladas | Tipos MainActor podem estar em conformidade com protocolos sem soluções inseguras |
@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 verbosas para targets de aplicativos |
| Adoção opt-in | Caminho de migração sem quebra — habilite recursos incrementalmente |
Passos para Migração
- Habilitar no Xcode: Seção de Concorrência do Compilador Swift nas Configurações de Build
- 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: Ative o modo de inferência para targets de aplicativos
- Adicione
@concurrentonde necessário: Perfile primeiro, depois descarregue caminhos críticos - Teste minuciosamente: Problemas de data race tornam-se erros de compilação
Práticas Recomendadas
- Comece no MainActor — escreva código single-threaded primeiro, otimize depois
- Use
@concurrentapenas para trabalho intensivo de CPU — processamento de imagem, compressão, computação complexa - Ative o modo de inferência do MainActor para targets de aplicativos que são majoritariamente single-threaded
- Perfile antes de descarregar — use Instruments para encontrar gargalos reais
- Proteja globais com MainActor — estado mutável global/estático precisa de isolamento de actor
- Use conformidades isoladas em vez de soluções
nonisolatedou wrappers@Sendable - Migre incrementalmente — habilite recursos um de cada vez nas configurações de build
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 de
DispatchQueuequando actors fornecem a mesma segurança - Pular verificações de
model.availabilityem código de Foundation Models relacionado à concorrência - Brigar com o compilador — se ele reportar um data race, o código tem um problema real de concorrência
- Assumir que todo código
asyncroda em segundo plano (padrão do Swift 6.2: permanece no actor 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
- Resolvendo erros do compilador de segurança contra data races durante a adoção do Xcode 26
- Construindo arquiteturas de aplicativos centradas no MainActor (a maioria dos aplicativos de UI)
- Otimização de desempenho — descarregando computações pesadas específicas para segundo plano