View in English

  • Global Nav Open Menu Global Nav Close Menu
  • Apple Developer
Search
Cancel
  • Apple Developer
  • News
  • Discover
  • Design
  • Develop
  • Distribute
  • Support
  • Account
Only search within “”

Quick Links

5 Quick Links

Vídeos

Abrir menu Fechar menu
  • Coleções
  • Tópicos
  • Todos os vídeos
  • Sobre

Mais vídeos

  • Sobre
  • Resumo
  • Transcrição
  • Código
  • 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

    • Concurrency
    • Mutex
    • The Swift Programming Language: Concurrency
    • Updating an App to Use Swift Concurrency
      • Vídeo HD
      • Vídeo SD

    Vídeos relacionados

    WWDC25

    • Adote os recursos de concorrência do Swift
    • Sessão prática de codificação: aprimore um app usando a concorrência do Swift

    WWDC23

    • Explore SwiftUI animation
  • Buscar neste vídeo...

    Olá, pessoal, sejam bem-vindos. Eu sou seu guia, Daniel, da equipe SwiftUI.

    Juntos, vamos explorar o cenário de concorrência e desenvolvimento de app SwiftUI.

    Você está aqui porque ouviu as histórias sobre essas criaturas perigosas chamadas bugs de corrida de dados.

    Você pode ter encontrado algum no passado.

    Estou falando de estados de app inesperados, animações com falhas e até mesmo perdas de dados permanentes.

    Mas não se preocupe, nosso passeio é 100% seguro. Porque com Swift e SwiftUI, vamos deixar esses animais da corrida de dados para trás. O SwiftUI executa seu código de forma concorrente de várias maneiras. Neste passeio, você aprenderá como identificá-los por meio das anotações de concorrência das APIs SwiftUI. No final, espero que você saia com mais confiança e coragem nas suas próprias aventuras com o app SwiftUI.

    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.

    Tudo o que veremos aqui se aplica com ou sem esse novo modo. Este passeio conta com três atrações.

    Vamos começar pelos belos Campos do Ator principal e apreciar como SwiftUI trata o ator principal como o padrão de tempo de compilação e tempo de execução para apps.

    Em seguida, visitaremos os Penhascos da concorrência e veremos como o SwiftUI ajuda a evitar problemas na interface ao descarregar o trabalho da thread principal e, ao mesmo tempo, protege dos bugs de corrida de dados selvagens.

    Finalmente, chegaremos ao Campo, nos situaremos e contemplaremos a relação entre seu código concorrente e as APIs SwiftUI.

    Vamos à nossa primeira parada, os Campos do Ator principal.

    Durante nosso passeio, quero coletar alguns esquemas de cores inspirados na natureza, então criei um app para isso. Após tirar uma foto, posso escolher quantas cores quero e pressionar o botão Extrair. O app vai escolher cores complementares da foto e mostrá-las na tela.

    Posso rolar para baixo para ver todos os esquemas de cores que extraí e escolher o meu favorito para exportar.

    Para a interface de extração, fiz uma estrutura ColorExtractorView.

    Ela está em conformidade com o protocolo de visualização do SwiftUI, que declara isolamento do @MainActor.

    O Swift usa o isolamento de dados para entender e verificar a segurança de todos os estados mutáveis. Durante o passeio, encontraremos muitos conceitos de concorrência como esse. Se você é novo na concorrência do Swift ou só precisa de uma atualização, confira a sessão "Adote os recursos de concorrência do Swift". No SwiftUI, View é isolado no @MainActor, e eu conformo minha estrutura para View.

    Portanto, ColorExtractorView se torna @MainActor isolado. Essa linha pontilhada indica isolamento inferido, ou seja, essa anotação está implícita no tempo de compilação, mas na verdade não faz parte do código que escrevi. O tipo geral isolado no @MainActor significa que todos os seus membros também estão implicitamente isolados.

    Isso inclui a propriedade body que implementa o requisito de View, bem como outros membros que declaro, como essa variável @State.

    Olhando agora o corpo da visualização, me refiro a outras propriedades de membro, como o esquema do modelo ou uma vinculação ao colorCount do modelo.

    Isso é permitido pelo compilador porque o isolamento de @MainActor compartilhado garante que esses acessos sejam seguros.

    Isso também parece intuitivo.

    @MainActor é o padrão de tempo de compilação do SwiftUI. Isso significa que, na maioria das vezes, posso apenas me concentrar em criar os recursos do app, sem pensar muito sobre concorrência.

    Não preciso anotar o código para fins de concorrência. É seguro automaticamente.

    Para abrir algum espaço para mais código, vou apenas esconder esses isolamentos inferidos.

    Esse padrão de tempo de compilação com @MainActor vai além do código síncrono na minha visualização.

    Os tipos do meu modelo de dados não precisam de anotações de @MainActor.

    Como instancio o modelo dentro da declaração da visualização, o Swift garantirá que a instância do modelo seja isolada corretamente.

    Este SchemeContentView tem um gesto de toque que inicia o trabalho de extração de cores.

    A função de extração de cores é assíncrona, então estou usando uma Task para alternar para um contexto assíncrono, a fim de chamá-la.

    Como o corpo da visualização é isolado no @MainActor, o fechamento que dei a essa tarefa também é executado na thread principal, o que é conveniente.

    O isolamento do @MainActor é o padrão de tempo de compilação do SwiftUI.

    Isso torna as visualizações de escrita convenientes e acessíveis. Mas, há outra razão muito prática para isso. APIs de AppKit e UIKit são exclusivamente isoladas no @MainActor.

    O SwiftUI interopera perfeitamente com esses frameworks. Por exemplo, o protocolo UIViewRepresentable refina o protocolo View. Semelhante a uma estrutura, isso isola UIViewRepresentable no @MainActor.

    Portanto, um tipo que está em conformidade com UIViewRepresentable também é um View. Por isso, é isolado no @MainActor.

    O inicializador do UILabel requer isolamento no @MainActor. E isso funciona no meu makeUIView, porque o makeUIView é um membro do meu tipo representável isolado no @MainActor.

    Não há necessidade de anotar com @MainActor. O SwiftUI anota suas APIs com @MainActor, porque isso reflete o comportamento de tempo de execução padrão que ele implementa.

    Essas anotações estão após a semântica pretendida pelo framework em tempo de execução.

    As anotações de concorrência do SwiftUI expressam sua semântica de tempo de execução. Isso Pode parecer uma distinção sutil das conveniências de tempo de compilação que vimos anteriormente, mas é fundamental. Veremos outro exemplo que reforça essa ideia agora.

    Certo, pessoal, a próxima parada vai ser emocionante. Apertem seus cintos de segurança e verifiquem se seus dispositivos eletrônicos estão seguros.

    Conforme você introduz mais recursos no app durante o desenvolvimento, caso a thread principal tenha muito trabalho a fazer, o app pode começar a ter quedas ou lentidão. Você pode usar tarefas e concorrência estruturada para descarregar a computação da thread principal. A sessão "Aprimore um app usando a concorrência do Swift" fornece uma série de técnicas práticas para melhorar o desempenho do seu app. Não o deixe escapar!

    O foco do passeio é ver como o SwiftUI usa a concorrência do Swift para melhorar o desempenho dos seus apps.

    No passado, a equipe do SwiftUI revelou que animações internas usam uma thread em segundo plano para calcular seus estados intermediários.

    Vamos rever isso, investigando esse círculo dentro do meu SchemeContentView.

    Conforme o trabalho de extração de cores começa e termina, o círculo aumenta e diminui de volta ao seu tamanho original com animação.

    Para isso, estou usando um scaleEffect que reage à propriedade isLoading.

    Cada quadro desta animação requer um valor de escala diferente entre 1 e 1,5.

    Valores animados como dessa escala envolvem cálculos matemáticos complexos. Calcular muitos desses, quadro a quadro, pode ser caro. Portanto, o SwiftUI executa esse cálculo em uma thread em segundo plano, para que a thread principal tenha mais capacidade para outras coisas.

    Essa otimização também se aplica às APIs implementadas.

    Isso mesmo. Às vezes, o SwiftUI executa seu código fora da thread principal.

    Mas não se preocupe, não é tão complicado assim.

    O SwiftUI é declarativo. Ao contrário do UIView, a estrutura que está em conformidade com o protocolo View não é um objeto que precisa ocupar um local fixo na memória.

    No tempo de execução, o SwiftUI cria uma representação separada para o View.

    Essa representação oferece oportunidades para muitos tipos de otimizações. Uma importante é avaliar partes da representação do View em uma thread em segundo plano.

    O SwiftUI reserva essa técnica para ocasiões em que muita computação é feita em seu nome. Por exemplo, na maioria das vezes, envolve cálculos geometria de alta frequência.

    O protocolo Shape é um exemplo disso.

    O protocolo Shape requer um método que retorna um caminho.

    Fiz um formato de cunha personalizado para representar uma cor extraída na minha roda.

    Ele implementa esse método de caminho.

    Cada cunha tem uma orientação distinta. Enquanto esse formato de cunha está sendo animado, o método de caminho que escrevi recebe chamadas de uma thread em segundo plano.

    Outro tipo de lógica personalizada que o SwiftUI executa em seu nome é um argumento de encerramento.

    No meio do círculo estão esses textos borrados.

    Para implementar isso, estou usando um visualEffect em um texto do SwiftUI.

    Ele altera o raio de desfoque entre dois valores à medida que o valor do pulse alterna entre true e false. O modificador do view visualEffect recebe um encerramento para definir efeitos na visualização de assunto, ou seja, o texto. Os efeitos visuais podem ficar chiques e caros de renderizar.

    Assim, o SwiftUI pode optar por chamar esse encerramento de uma thread em segundo plano.

    Então são duas APIs que podem chamar seu código de uma thread em segundo plano. Vamos conhecer mais alguns.

    O protocolo Layout pode chamar seus métodos de requisito fora da thread principal. E, semelhante ao visualEffect, o primeiro argumento de onGeometryChange é um encerramento que pode ser chamado pela thread em segundo plano também.

    Essa otimização de tempo de execução com uma thread em segundo plano faz parte do SwiftUI há muito tempo.

    O SwiftUI pode expressar esse comportamento de tempo de execução, ou semântica, para o compilador e você, com a anotação Sendable.

    Aqui, de novo, as anotações de concorrência do SwiftUI expressam sua semântica de tempo de execução.

    Executar o código em uma thread separada libera a thread principal, para que seu app responda melhor.

    E a palavra-chave Sendable está aqui para lembrar sobre as possíveis condições de corrida de dados quando você precisar compartilhar dados do @MainActor.

    Pense no Sendable como uma placa de aviso em uma trilha à beira do penhasco que diz "Perigo! Não corra aqui!" Essa descrição talvez seja um pouco dramática demais. Na prática, o Swift encontrará de forma confiável quaisquer condições de corrida em potencial no código e lembrará delas com erros do compilador. A melhor estratégia para evitar condições de corrida de dados é não compartilhar dados entre tarefas concorrentes.

    Quando uma API SwiftUI requer que você escreva uma função Sendable, o framework fornecerá a maioria das variáveis necessárias como argumentos de função. Aqui está um breve exemplo.

    Anteriormente, havia um detalhe em ColorExtactorView que eu não mostrei. A roda de cores e o controle deslizante têm a mesma largura, graças a este tipo EqualWidthVStack.

    EqualWidthVStack é um layout personalizado.

    Como ele faz o layout não é o nosso foco. O ponto aqui é que posso fazer todos esses cálculos sofisticados com o argumento que o SwiftUI envia, sem tocar em nenhuma variável externa.

    Mas, e se eu realmente precisar acessar algumas variáveis externas a uma função Sendable?

    No SchemeContentView, preciso do pulso de estado neste visualEffect.

    Mas o Swift diz que há uma condição potencial de corrida de dados.

    Vamos pegar nossos binóculos e focar no que o erro do compilador está nos dizendo.

    A variável pulse é uma abreviação de self.pulse. Esse é um cenário comum ao compartilhar uma variável isolada no @MainActor em fechamentos enviáveis.

    O self é um view. Ele está isolado no ator principal. Esse é o nosso ponto de partida. A partir daí, nosso objetivo final é acessar a variável pulse em um encerramento Sendable. Para isso, duas coisas devem acontecer.

    Primeiro, o valor self deve cruzar o limite do ator principal para a região de código de threads em segundo plano.

    No Swift, nos referimos a isso como enviar a variável self para a thread em segundo plano.

    Isso requer que o tipo de self seja Sendable.

    Agora que o self aparece no lugar certo, queremos ler seu pulse de propriedade nesta região não isolada. O compilador não permitirá isso, a menos que o pulse de propriedade não seja isolado em nenhum ator.

    Olhando para o código novamente, como self é um View, ele é protegido pelo @MainActor.

    Assim, o compilador o considera Sendable.

    Por causa disso, o Swift aceita o fato de que essa referência ao self atravessa de seu isolamento no @MainActor para o encerramento Sendable.

    Então, na verdade, o Swift está nos alertando sobre a tentativa de acessar a propriedade do pulse.

    É claro que sabemos que, como membro do View, o pulse é isolado no @MainActor.

    Então o compilador está me dizendo que, embora eu posso enviar o self aqui, acessar o pulso de propriedade isolado no @MainActor não é seguro.

    Para corrigir esse erro de compilação, posso evitar a leitura da propriedade por meio de uma referência ao View.

    O efeito visual que estou escrevendo não precisa de todo o valor desse View. Ele só quer saber se pulse é true ou false. Posso fazer uma cópia da variável pulse na lista de captura do encerramento e consultar a cópia. Dessa forma, não estou mais enviando o self para esse encerramento.

    Estou enviando uma cópia do pulse, que é Sendable porque Bool é um tipo de valor simples.

    Essa cópia existe apenas dentro do escopo dessa função, então acessá-la aqui não causa nenhum problema de corrida de dados.

    Nesse exemplo, não foi possível acessar essa variável pulse em um encerramento Sendable, porque ela é protegida por um ator global.

    Outra estratégia para que isso dê certo é fazer com que tudo o que estamos lendo não fique isolado.

    Certo, pessoal, vocês chegaram ao Acampamento. Vamos nos sentar e conversar sobre como organizar seu código concorrente.

    Os desenvolvedores SwiftUI experientes devem ter notado que a maioria das APIs do SwiftUI, como o callback de ação do botão, são síncronas.

    Para chamar seu código concorrente, primeiro você precisa alternar para um contexto assíncrono com uma Task.

    Mas por que o botão não aceita um encerramento assíncrono?

    As atualizações síncronas são importantes para uma boa experiência do usuário. Isso é extremamente importante se o seu app tiver tarefas de longa duração e as pessoas tiverem que esperar pelos resultados.

    Antes de iniciar uma tarefa de longa duração com uma função assíncrona, é importante atualizar a interface para indicar que a tarefa está em andamento.

    Essa atualização deve ser síncrona, especialmente se precisar disparar alguma animação sensível ao tempo.

    Imagine se eu pedir a um modelo de linguagem para me ajudar a extrair as cores. Esse processo de extração vai demorar um pouco. Então, no meu app, vou usar withAnimation para acionar de forma síncrona vários estados de carregamento. Quando a tarefa é concluída, eu posso reverter esses estados de carregamento por outra alteração de estado síncrona.

    Os callbacks de ação do SwiftUI aceitam encerramentos síncronos, necessários para configurar atualizações da interface, como estados de carregamento.

    As funções assíncronas, por outro lado, exigem consideração extra, ainda mais ao trabalhar com animações. Vamos explorar isso agora.

    No meu app, posso rolar para cima para revelar um histórico dos esquemas de cores anteriores. Conforme cada esquema aparece na tela, quero que suas cores sejam reveladas com um pouco de animação.

    O modificador de visualização onScrollVisibilityChange apresenta o evento quando o esquema de cores aparece na tela. Quando acontecer, vou definir uma variável de estado como true para acionar a animação, o que faz com que o deslocamento Y de cada cor seja atualizado com a animação.

    Como um framework da interface, para criar interações suaves a cada quadro, o SwiftUI precisa aceitar que os dispositivos exigem certa taxa de atualização da tela.

    Esse é um contexto importante quando quero que meu código reaja a um gesto contínuo como rolar. Vamos colocar esse código na linha do tempo.

    Vou usar esse triângulo verde para marcar o momento em que o SwiftUI chama onScrollVisibilityChange. E o círculo azul marca o momento em que aciono minha animação com uma mutação de estado.

    Com essa configuração, se essa mutação ocorrer no mesmo quadro com o callback chamada de gesto pode fazer uma grande diferença visualmente.

    Imagine que eu queira adicionar algum trabalho assíncrono antes da minha mutação animada. Vou marcar o momento em que o trabalho assíncrono começa com uma linha laranja e aguardar.

    No Swift, aguardar uma função assíncrona cria um ponto de suspensão.

    Uma tarefa aceita uma função assíncrona como um argumento.

    Quando o compilador vê uma espera, ele divide a função assíncrona em duas partes.

    Após executar a primeira parte, o tempo de execução do Swift pode pausar essa função e fazer algum outro trabalho na CPU. Isso pode durar um tempo arbitrário. Em seguida, o tempo de execução é retomado na função assíncrona original e executa sua segunda metade.

    Esse processo pode se repetir para cada ocorrência de aguardar na função.

    Voltando à linha do tempo, essa suspensão pode significar que o encerramento da tarefa não será retomado até muito mais tarde, ultrapassando o prazo de atualização do dispositivo.

    Para o usuário, isso significa que minha animação parece lenta e fora de sintonia. Portanto, uma mutação em uma função assíncrona pode não ajudar a alcançar seu objetivo.

    O SwiftUI fornece callbacks síncronos por padrão. Isso ajuda a evitar a suspensão não intencional do código assíncrono. Atualizar a interface dentro de encerramentos de ação síncrona é fácil de fazer corretamente. Você sempre tem a opção de usar uma tarefa para ativar um contexto assíncrono.

    A lógica sensível ao tempo, como a animação, requer que a entrada e a saída do SwiftUI sejam síncronas. Mutações síncronas de propriedades observáveis e callbacks síncronos são os tipos mais naturais de interação com o framework. Uma ótima experiência de usuário não precisa envolver muita lógica de concorrência personalizada. O código síncrono é um ótimo ponto de partida e término para muitos apps.

    Por outro lado, se o app fizer muito trabalho concorrente, tente encontrar os limites entre o código da interface e o código que não é da interface.

    É melhor separar a lógica do trabalho assíncrono da lógica de visualização.

    Você pode usar um trecho do estado como ponte. O estado desacopla o código da interface do código assíncrono.

    Ele pode iniciar as tarefas assíncronas.

    Conforme algum trabalho assíncrono for concluído, execute uma mutação síncrona no estado, para que sua interface possa ser atualizada como reações a essa alteração.

    Dessa forma, a lógica da interface é principalmente síncrona.

    Como bônus, será mais fácil escrever testes para seu código assíncrono, já que agora ele é independente da lógica da interface.

    Seu View ainda pode usar uma tarefa para alternar para um contexto assíncrono.

    Mas tente manter o código nesse contexto assíncrono simples. Ele está lá para informar o modelo sobre um evento de interface.

    Encontrar os limites entre o código da interface que requer muitas alterações sensíveis ao tempo e a lógica assíncrona de longa execução é uma ótima maneira de melhorar a estrutura de um app.

    Isso pode ajudar você a manter as exibições síncronas e responsivas. Também é importante organizar o código que não é da interface.

    Você terá maior liberdade para fazer isso com as dicas que mostrei neste acampamento.

    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 Sendable. 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.

    É isso aí, pessoal. É assim que o SwiftUI usa a concorrência do Swift para ajudar a criar apps rápidos e sem corrida de dados.

    Para encerrar o passeio, espero que você tenha obtido um modelo mental sólido para concorrência no SwiftUI.

    Agradeço o passeio e desejo muitas aventuras épicas.

    • 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
    • Assim, o SwiftUI usa a simultaneidade 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 simultaneamente de várias maneiras e fornece anotações de simultaneidade em suas APIs para ajudar desenvolvedores identificar e gerenciar simultaneidade. Esta sessão se concentra em entender como o SwiftUI lida com a simultaneidade 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 no 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 os 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 exibiçã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.

Developer Footer

  • Vídeos
  • WWDC25
  • Explore a concorrência no SwiftUI
  • Open Menu Close Menu
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • App Store
    • Audio & Video
    • Augmented Reality
    • Business
    • Design
    • Distribution
    • Education
    • Fonts
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Machine Learning & AI
    • Open Source
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Sample Code
    • Tutorials
    • Downloads
    • Forums
    • Videos
    Open Menu Close Menu
    • Support Articles
    • Contact Us
    • Bug Reporting
    • System Status
    Open Menu Close Menu
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles
    • Feedback Assistant
    Open Menu Close Menu
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program
    • News Partner Program
    • Video Partner Program
    • Security Bounty Program
    • Security Research Device Program
    Open Menu Close Menu
    • Meet with Apple
    • Apple Developer Centers
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Academies
    • WWDC
    Get the Apple Developer app.
    Copyright © 2025 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines