-
Explore a concorrência no SwiftUI
Descubra como o SwiftUI usa a concorrência no Swift para criar apps seguros e responsivos. Explore como o SwiftUI usa o ator principal por padrão e transfere o trabalho para outros atores. Aprenda a interpretar anotações de concorrência e gerenciar tarefas assíncronas com o loop de eventos do SwiftUI para atualização da interface de usuário e criação de animações suaves. Ao final, você saberá como evitar corridas de dados e como escrever código com confiança.
Capítulos
- 0:00 - Introdução
- 2:13 - Campos do ator principal
- 7:17 - Penhascos da concorrência
- 16:53 - Acampamento do código
- 23:47 - Próximas etapas
Recursos
- Mutex
- Concurrency
- Updating an App to Use Swift Concurrency
- The Swift Programming Language: Concurrency
Vídeos relacionados
WWDC25
- Adote os recursos de concorrência do Swift
- Codificação guiada: aprimore um app usando a concorrência do Swift
WWDC23
-
Buscar neste vídeo...
-
-
2:45 - UI for extracting colors
// UI for extracting colors struct ColorScheme: Identifiable, Hashable { var id = UUID() let imageName: String var colors: [Color] } @Observable final class ColorExtractor { var imageName: String var scheme: ColorScheme? var isExtracting: Bool = false var colorCount: Float = 5 func extractColorScheme() async {} } struct ColorExtractorView: View { @State private var model = ColorExtractor() var body: some View { ImageView( imageName: model.imageName, isLoading: model.isExtracting ) EqualWidthVStack { ColorSchemeView( isLoading: model.isExtracting, colorScheme: model.scheme, extractCount: Int(model.colorCount) ) .onTapGesture { guard !model.isExtracting else { return } withAnimation { model.isExtracting = true } Task { await model.extractColorScheme() withAnimation { model.isExtracting = false } } } Slider(value: $model.colorCount, in: 3...10, step: 1) .disabled(model.isExtracting) } } } } -
5:55 - AppKit and UIKit require @MainActor: an example
// AppKit and UIKit require @MainActor // Example: UIViewRepresentable struct FancyUILabel: UIViewRepresentable { func makeUIView(context: Context) -> UILabel { let label = UILabel() // customize the label... return label } } -
6:42 - UI for extracting colors
// UI for extracting colors struct ColorScheme: Identifiable, Hashable { var id = UUID() let imageName: String var colors: [Color] } @Observable final class ColorExtractor { var imageName: String var scheme: ColorScheme? var isExtracting: Bool = false var colorCount: Float = 5 func extractColorScheme() async {} } struct ColorExtractorView: View { @State private var model = ColorExtractorModel() var body: some View { ImageView( imageName: model.imageName, isLoading: model.isExtracting ) EqualWidthVStack(spacing: 30) { ColorSchemeView( isLoading: model.isExtracting, colorScheme: model.scheme, extractCount: Int(model.colorCount) ) .onTapGesture { guard !model.isExtracting else { return } withAnimation { model.isExtracting = true } Task { await model.extractColorScheme() withAnimation { model.isExtracting = false } } } Slider(value: $model.colorCount, in: 3...10, step: 1) .disabled(model.isExtracting) } } } } -
8:26 - Animated circle, part of color scheme view
// Part of color scheme view struct SchemeContentView: View { let isLoading: Bool @State private var pulse: Bool = false var body: some View { ZStack { // Color wheel … Circle() .scaleEffect(isLoading ? 1.5 : 1) VStack { Text(isLoading ? "Please wait" : "Extract") if !isLoading { Text("^[\(extractCount) color](inflect: true)") } } .visualEffect { [pulse] content, _ in content .blur(radius: pulse ? 2 : 0) } .onChange(of: isLoading) { _, newValue in withAnimation(newValue ? kPulseAnimation : nil) { pulse = newValue } } } } } -
13:10 - UI for extracting colors
// UI for extracting colors struct ColorExtractorView: View { @State private var model = ColorExtractor() var body: some View { ImageView( imageName: model.imageName, isLoading: model.isExtracting ) EqualWidthVStack { ColorSchemeView( isLoading: model.isExtracting, colorScheme: model.scheme, extractCount: Int(model.colorCount) ) .onTapGesture { guard !model.isExtracting else { return } withAnimation { model.isExtracting = true } Task { await model.extractColorScheme() withAnimation { model.isExtracting = false } } } Slider(value: $model.colorCount, in: 3...10, step: 1) .disabled(model.isExtracting) } } } } -
13:47 - Part of color scheme view
// Part of color scheme view struct SchemeContentView: View { let isLoading: Bool @State private var pulse: Bool = false var body: some View { ZStack { // Color wheel … Circle() .scaleEffect(isLoading ? 1.5 : 1) VStack { Text(isLoading ? "Please wait" : "Extract") if !isLoading { Text("^[\(extractCount) color](inflect: true)") } } .visualEffect { [pulse] content, _ in content .blur(radius: pulse ? 2 : 0) } .onChange(of: isLoading) { _, newValue in withAnimation(newValue ? kPulseAnimation : nil) { pulse = newValue } } } } } -
17:42 - UI for extracting colors
// UI for extracting colors struct ColorExtractorView: View { @State private var model = ColorExtractor() var body: some View { ImageView( imageName: model.imageName, isLoading: model.isExtracting ) EqualWidthVStack { ColorSchemeView( isLoading: model.isExtracting, colorScheme: model.scheme, extractCount: Int(model.colorCount) ) .onTapGesture { guard !model.isExtracting else { return } withAnimation { model.isExtracting = true } Task { await model.extractColorScheme() withAnimation { model.isExtracting = false } } } Slider(value: $model.colorCount, in: 3...10, step: 1) .disabled(model.isExtracting) } } } } -
18:55 - Animate colors as they appear by scrolling
// Animate colors as they appear by scrolling struct SchemeHistoryItemView: View { let scheme: ColorScheme @State private var isShown: Bool = false var body: some View { HStack(spacing: 0) { ForEach(scheme.colors) { color in color .offset(x: 0, y: isShown ? 0 : 60) } } .onScrollVisibilityChange(threshold: 0.9) { guard !isShown else { return } withAnimation { isShown = $0 } } } }
-
-
- 0:00 - Introdução
O SwiftUI usa a concorrência do Swift para ajudar desenvolvedores a criar apps rápidos e sem corrida de dados. O Swift 6.2 apresenta um novo modo de linguagem, que marca todos os tipos em um módulo com a anotação @MainActor implicitamente. O SwiftUI executa código de maneira concorrente de diversas formas e fornece anotações de concorrência em suas APIs para ajudar desenvolvedores identificar e gerenciar essas concorrência. Esta sessão se concentra em entender como o SwiftUI lida com a concorrência para evitar corridas de dados e melhorar o desempenho do app.
- 2:13 - Campos do ator principal
O protocolo View do SwiftUI é isolado no @MainActor, fazendo dele o padrão de tempo de compilação para o código da IU. Isso significa que a maioria do código da IU é executada implicitamente na thread principal, simplificando o desenvolvimento e garantindo a compatibilidade com UIKit e AppKit. Os modelos de dados instanciados em um View também são isolados automaticamente. As anotações de @MainActor do SwiftUI refletem seu comportamento de tempo de execução e semântica pretendida, não apenas conveniências de tempo de compilação.
- 7:17 - Penhascos da concorrência
O SwiftUI usa threads em segundo plano para tarefas computacionalmente intensivas, como animações e cálculos de forma (por exemplo, método "path" do protocolo "Shape", fechamento "visualEffect", protocolo "Layout", fechamento "onGeometryChange") para evitar problemas na IU. A anotação Sendable sinaliza possíveis condições de corrida de dados ao compartilhar dados entre o ator principal e as threads em segundo plano. Para evitar corridas de dados, minimize o compartilhamento de dados. Quando o compartilhamento for necessário, faça cópias dos dados.
- 16:53 - Acampamento do código
Os retornos de chamada de ação do SwiftUI são síncronos por design, para garantir atualizações imediatas da IU, principalmente animações e estados de carregamento. As tarefas de longa duração devem ser iniciadas de forma assíncrona, mas as atualizações da IU devem permanecer síncronas. Separe a lógica da IU da que não é da IU (assíncrona), usando o estado como ponte para disparar atualizações da IU após concluir tarefas assíncronas. Mantenha o código assíncrono na visualização simples e concentre-se em informar o modelo de eventos da IU. A lógica sensível ao tempo requer que a entrada e a saída do SwiftUI sejam síncronas.
- 23:47 - Próximas etapas
O Swift 6.2 vem com uma ótima configuração de isolamento de ator padrão. Se você já tiver um app, experimente. Você poderá apagar a maioria das anotações de @MainActor. O Mutex é uma ferramenta importante para tornar uma classe enviável. Confira sua documentação oficial para saber como. Desafie-se a escrever alguns testes de unidade para o código assíncrono no seu app. Veja se é possível fazer isso sem importar do SwiftUI.