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

Voltar para WWDC25

  • Sobre
  • Transcrição
  • Código
  • Sessão prática de codificação: aprimore um app usando a concorrência do Swift

    Saiba como otimizar a experiência de usuário do seu app com a concorrência do Swift enquanto atualizamos um app de exemplo existente. Começaremos com um app que será o ator principal e, em seguida, apresentaremos gradualmente o código assíncrono, conforme necessário. Usaremos tarefas para otimizar a execução de código no ator principal e descobriremos como paralelizar o código transferindo o trabalho para o segundo plano. Vamos explorar o que a segurança na corrida de dados oferece e interpretar e corrigir erros de segurança na corrida de dados. Por fim, mostraremos como você pode aproveitar ao máximo a concorrência estruturada no contexto de um app.

    Capítulos

    • 0:00 - Introdução
    • 2:11 - Configuração de concorrência acessível
    • 2:51 - Arquitetura do app de exemplo
    • 3:42 - Carregamento de fotos da fototeca de forma assíncrona
    • 9:03 - Extrair o adesivo e as cores da foto
    • 12:30 - Executar tarefas em uma thread em segundo plano
    • 15:58 - Tarefas em paralelo
    • 18:44 - Evitar corridas de dados com o Swift 6
    • 27:56 - Controlar código assíncrono com concorrência estruturada
    • 31:36 - Conclusão

    Recursos

    • Code-along: Elevating an app with Swift concurrency
    • Swift Migration Guide
      • Vídeo HD
      • Vídeo SD

    Vídeos relacionados

    WWDC25

    • Adote os recursos de concorrência do Swift
    • Explore a concorrência no SwiftUI

    WWDC23

    • Analyze hangs with Instruments
    • Beyond the basics of structured concurrency
  • Buscar neste vídeo...

    Olá! Meu nome é Sima. Trabalho no Swift e SwiftUI. Neste vídeo, você vai aprender a aperfeiçoar seu app com a concorrência do Swift. Como desenvolvedores de apps, a maior parte do código que criamos está na thread principal.

    O código de thread única é fácil de entender e manter. Mas, ao mesmo tempo, um app moderno geralmente precisa executar tarefas demoradas, como uma solicitação de rede ou um cálculo caro. Nesses casos, o ideal é mover o trabalho para fora da thread principal para manter a capacidade de resposta do app. O Swift oferece todas as ferramentas necessárias para criar o código concorrente com confiança. Nesta sessão, vou mostrar como fazer isso criando um app com você. Vamos começar com um app de thread única e apresentar gradualmente o código assíncrono, conforme necessário. Em seguida, vamos melhorar o desempenho do app transferindo algumas das tarefas caras e executando-as em paralelo. Depois, vamos discutir alguns cenários comuns de segurança de corrida de dados que você pode encontrar e explorar maneiras de abordá-los. Finalmente, vamos falar sobre a concorrência estruturada e mostrar como usar ferramentas, como TaskGroup, para ter mais controle sobre o código concorrente. Mal posso esperar para começar. Eu adoro fazer diário e decorar minhas entradas com adesivos, então vou explicar como criar um app para compor pacotes de adesivos usando um conjunto de fotos. Nosso app terá duas visualizações principais. A primeira visualização vai ter um carrossel com todos os adesivos com um gradiente refletindo as cores da foto original, e a segunda vai ter uma grade de todo o pacote de adesivos, pronta para ser exportada. Sinta-se à vontade para baixar o app de amostra abaixo para acompanhar. Quando criei o projeto, o Xcode habilitou alguns recursos que fornecem um caminho mais acessível para a introdução da concorrência, incluindo o agente principal pelo modo padrão e alguns recursos futuros. Esses recursos estão ativados por padrão em novos projetos de app no Xcode 26.

    Na configuração de concorrência acessível, o modo de linguagem Swift 6 fornecerá segurança de corrida de dados sem introduzir a concorrência até que tudo esteja pronto. Se quiser habilitar esses recursos em projetos existentes, consulte o guia de migração do Swift para saber como.

    No código, o app terá duas visualizações principais: StickerCarousel e StickerGrid. Essas visualizações usam os adesivos que a estrutura do PhotoProcessor é responsável por extrair.

    O PhotoProcessor obtém a imagem bruta da fototeca antes de retornar o adesivo.

    A view StickerGrid tem um ShareLink que pode ser usado para compartilhar os adesivos.

    O tipo PhotoProcessor executa duas operações caras: a extração de adesivos e o cálculo de cores dominantes. Vamos ver como os recursos de concorrência do Swift podem nos ajudar a otimizar a experiência do usuário, sem impedir que o dispositivo execute tarefas caras. Vou começar com a view StickerCarousel. Essa view exibe os adesivos em rolagem horizontal. Dentro da scrollView, há um ForEach que itera pela matriz das fotos selecionadas da fototeca armazenada no modelo de view. Ele verifica o dicionário processedPhotos no viewModel para obter a foto processada correspondente à seleção na fototeca. Atualmente, não temos nenhuma foto processada, já que eu não criei nenhum código para obter uma imagem do seletor de fotos. Se eu executar o app agora, teremos a view StickerPlaceholder na scrollView. Vou navegar até StickerViewModel usando Command + clique. O StickerViewModel armazena uma matriz de fotos atualmente selecionadas da fototeca, representadas como um tipo SelectedPhoto. Vou abrir a Ajuda Rápida com a tecla Option para saber mais sobre esse tipo.

    SelectedPhoto é um tipo identificável que armazena um PhotosPickerItem do framework PhotosUI e o ID associado. O modelo também tem o dicionário chamado processedPhotos que mapeia o ID da foto selecionada para a imagem SwiftUI que ela representa. Eu já comecei a trabalhar na função loadPhoto que tira a foto selecionada. Mas atualmente ele não carrega dados do item do seletor de fotos que armazena. O PhotosPickerItem está em conformidade com o protocolo Transferable do SDK, o que permite carregar a representação solicitada com a função loadTransferable assíncrona. Vou solicitar a representação dos Dados.

    Agora, temos um erro de compilador.

    Isso ocorre porque a chamada para "loadTransferable" é assíncrona, e a função "loadPhoto" onde eu a chamo não está configurada para lidar com chamadas assíncronas. Sendo assim, o Swift ajuda sugerindo marcar "loadPhoto" com a palavra-chave assíncrona. Vou aplicar essa sugestão.

    Nossa função pode lidar com código assíncrono. Porém, ainda há mais um erro. Embora "loadPhoto" possa lidar com chamadas assíncronas, precisamos dizer o que esperar. Para fazer isso, preciso marcar a chamada para "loadTransferable" com a palavra-chave "await". Vou aplicar a correção sugerida.

    Vou chamar essa função na view StickerCarousel. Com Command + Shift + O, posso usar a função abrir rapidamente do Xcode para voltar ao StickerCarousel.

    Quero chamar a função loadPhoto quando a view StickerPlaceholder for exibida. Como essa função é assíncrona, vou usar o modificador de tarefa SwiftUI para iniciar o processamento de fotos quando essa view aparecer.

    Vamos ver como fica no meu aparelho.

    Ótimo, está funcionando. Vamos tentar selecionar algumas fotos para testá-lo.

    Ótimo! Parece que as imagens estão sendo carregadas da fototeca. A tarefa permite manter a interface do app responsiva enquanto a imagem está sendo carregada a partir dos dados. Como estou usando o LazyHStack para exibir as imagens, estou iniciando tarefas de carregamento de fotos apenas para visualizações que precisam ser renderizadas na tela para que o app não execute mais trabalho do que o necessário. Vamos falar sobre por que o async/await melhora a capacidade de resposta do app.

    Adicionamos a palavra-chave "await" ao chamar o método "loadTransferable" e anotamos a função "loadPhoto" com "async". A palavra-chave "await" marca um possível ponto de suspensão. Isso significa que, inicialmente, a função loadPhoto é iniciada na thread principal e, quando chama loadTransferable em await, fica suspensa enquanto aguarda a conclusão de loadTransferable. Enquanto loadPhoto está suspenso, o framework Transferable executa loadTransferable na thread em segundo plano. Quando loadTransferable estiver concluído, loadPhoto retomará a execução na thread principal e atualizará a imagem. A thread principal está livre para responder a eventos da interface e executar outras tarefas enquanto loadPhoto está suspenso. A palavra-chave await indica um ponto no código em que outros trabalhos podem acontecer enquanto a função está suspensa. Assim, terminamos de carregar as imagens da fototeca. Nesse processo, aprendemos o que significa código assíncrono, como criá-lo e como considerá-lo. Agora, vamos adicionar um código ao nosso app que extrairia o adesivo da foto, bem como suas cores principais que podemos usar para o gradiente de plano de fundo quando exibido em uma visualização de carrossel.

    Vou usar o Command + clique para voltar a loadPhoto, onde posso aplicar esses efeitos.

    O projeto já inclui um PhotoProcessor, que pega os dados, extrai as cores e o adesivo e devolve a foto processada. Em vez de fornecer a imagem básica dos dados, vou usar o PhotoProcessor.

    O PhotoProcessor retorna uma foto processada, então vou atualizar o tipo do dicionário.

    O ProcessedPhoto fornecerá o adesivo extraído da foto e as diversas cores para compor o gradiente.

    Eu já incluí a visualização GradientSticker no projeto que tira uma processedPhoto. Vou usar o recurso abrir rapidamente para navegar até ele.

    Essa visualização mostra um adesivo armazenado em uma foto processada sobre um gradiente linear em ZStack.

    Vou adicionar o GradientSticker no carrossel.

    No momento, no StickerCarousel, estamos apenas redimensionando a foto, mas agora que temos uma foto processada, podemos usar o GradientSticker.

    Vamos criar e executar o app para ver nossos adesivos.

    Está funcionando.

    Ah, não! Embora os adesivos estejam sendo extraídos, não está bom rolar pelo carrossel.

    Suspeito que o processamento de imagem seja muito caro. Eu categorizei o app usando o Instruments para confirmar isso. O traço mostra que nosso app tem travamentos graves.

    Se eu ampliar e olhar para o traço de pilha mais pesado, verei que o processador de fotos bloqueia a thread principal com as tarefas de processamento caras por mais de dez segundos. Se você quiser saber mais sobre como analisar travamentos no app, confira a sessão "Analyze hangs with Instruments". Agora, vamos falar mais sobre o trabalho que o app está fazendo na thread principal.

    A implementação de "loadTransferable" descarregou o trabalho para segundo plano para evitar que o trabalho de carregamento aconteça na thread principal.

    Agora que adicionamos o código de processamento de imagem, que está sendo executado na thread principal e leva muito tempo para terminar, a thread principal não pode receber atualizações de interface do usuário, como responder a gestos de rolagem, proporcionando uma experiência ruim ao usuário no app.

    Antes, adotamos uma API assíncrona do SDK, que descarregava o trabalho por nós. Agora, precisamos executar nosso próprio código em paralelo para corrigir o travamento. Podemos mover algumas transformações de imagem para o segundo plano. A transformação da imagem é composta por essas três operações. A obtenção da imagem bruta e atualização da imagem precisam interagir com a interface, então não podemos mover esse trabalho para segundo plano, mas podemos descarregar o processamento da imagem. Isso garantirá que a thread principal fique livre para responder a outros eventos enquanto o trabalho caro de processamento de imagem ocorre. Vamos dar uma olhada na estrutura do PhotoProcessor para entender como fazer isso.

    Como meu app está no modo de agente principal por padrão, o PhotoProcessor está vinculado ao @MainActor, o que significa que todos os seus métodos devem ser executados no agente principal. O método "process" chama os métodos para extrair adesivo e extrair cores, então preciso fazer com que todos os métodos desse tipo possam escapar do agente principal. Para fazer isso, posso marcar todo o tipo PhotoProcessor com nonisolated. Esse é um novo recurso introduzido no Swift 6.1. Quando o tipo é marcado com nonisolated, todas as propriedades e métodos ficam automaticamente não isolados.

    Agora que o PhotoProcessor não está vinculado ao MainActor, podemos aplicar o novo atributo "@concurrent" à função de processo e marcá-lo com "async". Isso solicita ao Swift que sempre alterne para uma thread em segundo plano ao executar esse método. Vou usar o recurso abrir rapidamente para navegar de volta para o PhotoProcessor.

    Primeiro, vou aplicar nonisolated no tipo para desacoplar o PhotoProcessor do agente principal e permitir que os métodos sejam chamados a partir do código concorrente.

    Agora que o PhotoProcessor não está isolado, para ter certeza de que o método de processo será chamado a partir da thread em segundo plano, vou aplicar @concurrent e async.

    Agora, vou navegar de volta para o StickerViewModel com o recurso abrir rapidamente.

    Aqui, no método loadPhoto, preciso sair da thread principal chamando o método de processo com a palavra-chave "await", que o Swift já sugere. Vou aplicar essa sugestão.

    Vamos criar e executar nosso app para ver se a mudança do trabalho do agente principal ajudou nos travamentos.

    Parece que não há mais travas na rolagem.

    Porém, mesmo que eu possa interagir com a interface, a imagem está demorando um pouco para aparecer quando rolo na interface. Manter a capacidade de resposta de um app não é o único fator que melhora a experiência do usuário. Se movermos o trabalho para fora da thread principal, mas demora muito tempo para obter resultados para o usuário, isso ainda pode causar uma experiência frustrante no app.

    Mudamos a operação de processamento de imagem para uma thread em segundo plano, mas ainda leva muito tempo para ela terminar. Vamos ver como otimizar essa operação com concorrência para que ela termine mais rápido. O processamento da imagem requer a extração de adesivos e das cores mas essas operações são independentes uma da outra. Assim, podemos executar essas tarefas em paralelo usando async let. Agora, o pool de threads concorrentes, que gerencia todos as threads em segundo plano, agendará essas duas tarefas para iniciar em duas threads em segundo plano diferentes de uma só vez. Isso permite aproveitar vários núcleos no telefone.

    Vou clicar no método de processo para adotar async let.

    Vou manter a tecla Control + Shift e a tecla de seta para baixo pressionadas para usar o cursor de várias linhas e adicionar async na frente das variáveis de adesivos e cores.

    Agora que fizemos essas duas chamadas em paralelo, precisamos aguardar os resultados para retomar nossa função de processo. Vamos corrigir todos esses problemas usando o menu Editor.

    Porém, ainda há mais um erro. Desta vez, trata-se de uma corrida de dados. Vamos entender esse erro.

    Esse erro significa que meu tipo de PhotoProcessor não é seguro para compartilhar entre tarefas concorrentes. Para entender o motivo, vamos analisar suas propriedades armazenadas. A única propriedade que o PhotoProcessor armazena é uma instância de ColorExtractor, necessária para extrair as cores da foto. A classe ColorExtractor calcula as cores dominantes que aparecem na imagem. Esse cálculo opera em dados de imagem mutáveis de baixo nível, incluindo buffers de pixel, então o tipo de extrator de cores não é seguro para acessar de forma concorrente.

    No momento, todas as operações de extração de cores têm a mesma instância do ColorExtractor. Isso leva ao acesso concorrente à mesma memória.

    Isso é chamado de "corrida de dados", que pode levar a bugs de tempo de execução, como falhas e comportamento imprevisível.

    O modo de linguagem Swift 6 os identificará no momento da compilação, o que define esse conjunto de bugs quando você cria código executado em paralelo. Com isso, o que seria um bug de tempo de execução complicado passa a ser um erro de compilador que pode ser resolvido imediatamente. Se você clicar no botão "ajuda" na mensagem de erro, poderá saber mais sobre esse erro no site do Swift. Há várias opções que você pode considerar ao tentar resolver uma corrida de dados. A escolha de um depende de como o código usa os dados compartilhados. A primeira coisa a se perguntar é se esse estado mutável precisa ser compartilhado entre códigos concorrentes. Em muitos casos, você pode simplesmente evitar compartilhá-lo. No entanto, há casos em que o estado precisa ser compartilhado pelo código. Se esse for o caso, considere extrair o que você precisa compartilhar para um tipo de valor que seja seguro enviar. Somente se nenhuma dessas soluções for aplicável à situação, considere isolar esse estado para um agente como o MainActor. Vamos descobrir se a primeira solução funciona no nosso caso. Embora seja possível refatorar esse tipo para trabalhar de forma diferente e lidar com várias operações concorrentes, podemos mover o extrator de cores para

    uma variável local na função extractColors, de modo que cada foto processada tenha sua própria instância do extrator de cores. Essa é a alteração de código correta, porque o extrator de cores destina-se a trabalhar em uma foto de cada vez. Queremos uma instância separada dele para cada tarefa de extração de cor. Com essa alteração, nada fora da função extractColors pode acessar o extrator de cores, o que impede a corrida de dados.

    Para fazer essa alteração, vamos mover a propriedade do extrator de cores para a função extractColors.

    Ótimo! Com a ajuda do compilador, conseguimos detectar e eliminar uma corrida de dados em nosso app. Agora, vamos executá-lo!

    Vejo que o app está mais rápido.

    Se eu coletar um rastreamento de criador de perfil em Instruments e abri-lo, não terei mais os travamentos. Vamos recapitular rapidamente as otimizações que fizemos com a concorrência do Swift. Ao adotar o atributo "@concurrent", movemos o código de processamento de imagem da thread principal. Também tornamos as operações de extração de adesivos e cores paralelas entre si usando "async let", deixando o app muito mais eficiente. As otimizações feitas com a concorrência do Swift devem sempre ser baseadas em dados de ferramentas de análise, como o instrumento de perfil de tempo. Se você pode tornar o código mais eficiente sem introduzir concorrência, faça sempre isso primeiro.

    O app agora está rápido. Vamos dar uma pausa no processamento de imagens e adicionar algo divertido.

    Vamos adicionar um efeito visual para os adesivos processados que fará com que o adesivo passado desapareça e desfoque. Vamos mudar para o Xcode para criar isso.

    Vou voltar ao StickerCarousel usando o navegador de projeto do Xcode.

    Agora, vou aplicar o efeito visual em cada imagem na scrollView usando o modificador visualEffect.

    Aqui, estou aplicando alguns efeitos à view. Quero alterar o deslocamento, o desfoque e a opacidade apenas no último adesivo da scrollView, então preciso acessar a propriedade selection do viewModel para verificar se o efeito visual foi aplicado ao último adesivo.

    Parece que há um erro de compilador porque estou tentando acessar o estado de view protegido pelo agente principal a partir do encerramento visualEffect. Como cálculos de efeito visual são caros, o SwiftUI o descarrega da thread principal para maximizar o desempenho do app.

    Se você quiser se aventurar e saber mais, confira nossa sessão "Explore os recursos de concorrência do SwiftUI". Esse erro está me dizendo que o encerramento será avaliado posteriormente em segundo plano. Vamos confirmar olhando para a definição do "visualEffect", na qual vou clicar a tecla Command pressionada.

    Na definição, esse encerramento é @Sendable, uma indicação do SwiftUI de que ele será avaliado em segundo plano.

    Nesse caso, SwiftUI chama o efeito visual novamente sempre que a seleção é alterada para que eu possa fazer uma cópia dele usando a lista de captura do encerramento.

    Agora, quando o SwiftUI chamar esse encerramento, operará em uma cópia do valor de seleção, tornando a operação livre de corrida de dados.

    Vamos ver como ficou o efeito visual.

    Está ótimo, e eu posso ver como a imagem anterior desfoca e desaparece à medida que rolo a tela.

    Nos dois cenários de corrida de dados que encontramos, a solução foi não compartilhar dados que podem ser alterados a partir de código concorrente. A principal diferença foi que, no primeiro exemplo, eu mesmo introduzi uma corrida de dados executando parte do código em paralelo. No segundo exemplo, porém, usei uma API do SwiftUI que descarrega o trabalho para a thread em segundo plano por mim.

    Se você deve compartilhar o estado mutável, há outras maneiras de protegê-lo. Os tipos de valor que podem ser enviados impedem que o tipo possa ser compartilhado por código concorrente. Por exemplo, os métodos extractSticker e extractColors são executados em paralelo e ambos usam os dados da mesma imagem. Mas não há nenhuma condição de corrida de dados nesse caso porque Data é um tipo de valor Sendable. Data também implementa a cópia na gravação, portanto, só serão copiados se tiverem mutação. Se você não puder usar um tipo de valor, considere isolar seu estado para o agente principal. Felizmente, o agente principal pelo modo padrão já faz isso por você. Por exemplo, nosso modelo é uma classe, e podemos acessá-lo a partir de uma tarefa concorrente. Como o modelo é marcado implicitamente com o MainActor, é seguro fazer referência a partir de código concorrente. O código terá que mudar para o agente principal para acessar o estado. Nesse caso, a classe é protegida pelo agente principal, mas o mesmo se aplica a outros agentes que você pode ter em seu código. Nosso app está ficando ótimo até agora. Mas ainda não parece completo.

    Para poder exportar os adesivos, vamos adicionar o modo de visualização de grade de adesivos que inicia uma tarefa de processamento de fotos para cada foto que ainda não foi processada e exibe todos os adesivos de uma só vez. Ele também terá um botão de compartilhamento que permite a exportação desses adesivos. Vamos voltar ao código.

    Primeiro, vou clicar com o botão Command pressionado para navegar até o StickerViewModel.

    Vou adicionar outro método ao nosso modelo, "processAllPhotos()".

    Aqui, quero iterar todas as fotos processadas salvas até agora no meu modelo e, se ainda houver fotos não processadas, quero iniciar várias tarefas paralelas para iniciar o processamento delas de uma só vez.

    Já usamos o async let antes, mas isso só funcionou porque sabíamos que havia apenas duas tarefas para iniciar: a extração do adesivo e das cores. Agora, precisamos criar uma tarefa para todas as fotos brutas na matriz, e pode haver qualquer quantidade dessas tarefas de processamento.

    APIs como o TaskGroup permitem ter mais controle sobre o trabalho assíncrono que o app precisa executar.

    Os grupos de tarefas fornecem controle refinado sobre as tarefas secundárias e seus resultados. O grupo de tarefas permite iniciar qualquer número de tarefas secundárias que podem ser executadas em paralelo.

    Cada tarefa secundária pode levar quantidades arbitrárias de tempo para ser concluída, portanto, elas podem ser feitas em uma ordem diferente. No nosso caso, as fotos processadas serão salvas em um dicionário, então a ordem não importa.

    O TaskGroup está em conformidade com AsyncSequence, então podemos iterar os resultados à medida que eles são gerados para armazená-los no dicionário. Finalmente, podemos aguardar que todo o grupo termine as tarefas secundárias. Vamos voltar ao código para adotar um grupo de tarefas. Para adotar o grupo de tarefas, vou declará-lo primeiro.

    Aqui, dentro do encerramento, tenho uma referência ao grupo ao qual posso adicionar tarefas de processamento de imagens. Vou iterar sobre a seleção salva no modelo.

    Se essa foto foi processada, então eu não preciso criar uma tarefa para ela.

    Vou começar uma nova tarefa de carregar os dados e processar a foto.

    Como o grupo é uma sequência assíncrona, posso iterar sobre ele para salvar a foto processada no dicionário processedPhotos quando estiver pronto.

    É isso! Agora estamos prontos para exibir nossos adesivos no StickerGrid.

    Vou usar o recurso abrir rapidamente para acessar o StickerGrid.

    Aqui, eu tenho uma propriedade de estado finishedLoading que indica se todas as fotos terminaram de processar.

    Se as fotos ainda não tiverem sido processadas, uma ProgressView será exibida. Vou chamar o método processAllPhotos() que acabamos de implementar.

    Depois que todas as fotos são processadas, podemos definir a variável de estado. Finalmente, vou adicionar o link de compartilhamento na barra de ferramentas para compartilhar os adesivos.

    Estou preenchendo os itens do link de compartilhamento com um adesivo para cada foto selecionada.

    Vamos executar o app.

    Vou tocar no botão StickerGrid. Graças ao TaskGroup, a grade de visualização começa a processar todas as fotos de uma só vez. Quando acaba, posso ver instantaneamente todos os adesivos. Por fim, usando o botão Compartilhar na barra de ferramentas, posso exportar todos os adesivos como arquivos que posso salvar.

    Em nosso app, os adesivos serão coletados na ordem em que forem processados. Mas você também pode acompanhar a ordem, e o grupo de tarefas tem muito mais recursos. Para saber mais, confira a sessão "Beyond the basics of structured concurrency".

    Parabéns! O app está feito, e agora posso salvar meus adesivos. Adicionamos novos recursos a um app, descobrimos quando eles tiveram impacto na interface e usamos a concorrência necessária para melhorar a capacidade de resposta e o desempenho. Também aprendemos sobre concorrência estruturada e como evitar corridas de dados.

    Se você não acompanhou, ainda pode baixar a versão final do app e fazer alguns adesivos com suas próprias fotos. Para se familiarizar com os novos recursos e técnicas de concorrência do Swift mencionados aqui, tente otimizar ou ajustar ainda mais o app. Por fim, veja se consegue trazer essas técnicas para o seu app. Não se esqueça de categorizá-las primeiro. Para se aprofundar na compreensão dos conceitos do modelo de concorrência do Swift, confira a sessão "Adote os recursos de concorrência do Swift". Para migrar o projeto existente para adotar novos recursos de concorrência acessíveis, confira o "Guia de migração Swift". O que mais gostei foi que ganhei alguns adesivos para o meu caderno. Agradeço sua participação.

    • 6:29 - Asynchronously loading the selected photo from the photo library

      func loadPhoto(_ item: SelectedPhoto) async {
          var data: Data? = try? await item.loadTransferable(type: Data.self)
      
          if let cachedData = getCachedData(for: item.id) { data = cachedData }
      
          guard let data else { return }
          processedPhotos[item.id] = Image(data: data)
      
          cacheData(item.id, data)
      }
    • 6:59 - Calling an asynchronous function when the SwiftUI View appears

      StickerPlaceholder()
          .task {
              await viewModel.loadPhoto(selectedPhoto)
          }
    • 9:45 - Synchronously extracting the sticker and the colors from a photo

      func loadPhoto(_ item: SelectedPhoto) async {
          var data: Data? = try? await item.loadTransferable(type: Data.self)
      
          if let cachedData = getCachedData(for: item.id) { data = cachedData }
      
          guard let data else { return }
          processedPhotos[item.id] = PhotoProcessor().process(data: data)
      
          cacheData(item.id, data)
      }
    • 9:56 - Storing the processed photo in the dictionary

      var processedPhotos = [SelectedPhoto.ID: ProcessedPhoto]()
    • 10:45 - Displaying the sticker with a gradient background in the carousel

      import SwiftUI
      import PhotosUI
      
      struct StickerCarousel: View {
          @State var viewModel: StickerViewModel
          @State private var sheetPresented: Bool = false
      
          var body: some View {
              ScrollView(.horizontal) {
                  LazyHStack(spacing: 16) {
                      ForEach(viewModel.selection) { selectedPhoto in
                          VStack {
                              if let processedPhoto = viewModel.processedPhotos[selectedPhoto.id] {
                                  GradientSticker(processedPhoto: processedPhoto)
                              } else if viewModel.invalidPhotos.contains(selectedPhoto.id) {
                                  InvalidStickerPlaceholder()
                              } else {
                                  StickerPlaceholder()
                                      .task {
                                          await viewModel.loadPhoto(selectedPhoto)
                                      }
                              }
                          }
                          .containerRelativeFrame(.horizontal)
                      }
                  }
              }
              .configureCarousel(
                  viewModel,
                  sheetPresented: $sheetPresented
              )
              .sheet(isPresented: $sheetPresented) {
                  StickerGrid(viewModel: viewModel)
              }
          }
      }
    • 14:13 - Allowing photo processing to run on the background thread

      nonisolated struct PhotoProcessor {
       
          let colorExtractor = ColorExtractor()
      
          @concurrent
          func process(data: Data) async -> ProcessedPhoto? {
              let sticker = extractSticker(from: data)
              let colors = extractColors(from: data)
      
              guard let sticker = sticker, let colors = colors else { return nil }
      
              return ProcessedPhoto(sticker: sticker, colorScheme: colors)
          }
      
          private func extractColors(from data: Data) -> PhotoColorScheme? {
              // ...
          }
      
          private func extractSticker(from data: Data) -> Image? {
              // ...
          }
      }
    • 15:31 - Running the photo processing operations off the main thread

      func loadPhoto(_ item: SelectedPhoto) async {
          var data: Data? = try? await item.loadTransferable(type: Data.self)
      
          if let cachedData = getCachedData(for: item.id) { data = cachedData }
      
          guard let data else { return }
          processedPhotos[item.id] = await PhotoProcessor().process(data: data)
      
          cacheData(item.id, data)
      }
    • 20:55 - Running sticker and color extraction in parallel.

      nonisolated struct PhotoProcessor {
      
          @concurrent
          func process(data: Data) async -> ProcessedPhoto? {
              async let sticker = extractSticker(from: data)
              async let colors = extractColors(from: data)
      
              guard let sticker = await sticker, let colors = await colors else { return nil }
      
              return ProcessedPhoto(sticker: sticker, colorScheme: colors)
          }
      
          private func extractColors(from data: Data) -> PhotoColorScheme? {
              let colorExtractor = ColorExtractor()
              return colorExtractor.extractColors(from: data)
          }
      
          private func extractSticker(from data: Data) -> Image? {
              // ...
          }
      }
    • 24:20 - Applying the visual effect on each sticker in the carousel

      import SwiftUI
      import PhotosUI
      
      struct StickerCarousel: View {
          @State var viewModel: StickerViewModel
          @State private var sheetPresented: Bool = false
      
          var body: some View {
              ScrollView(.horizontal) {
                  LazyHStack(spacing: 16) {
                      ForEach(viewModel.selection) { selectedPhoto in
                          VStack {
                              if let processedPhoto = viewModel.processedPhotos[selectedPhoto.id] {
                                  GradientSticker(processedPhoto: processedPhoto)
                              } else if viewModel.invalidPhotos.contains(selectedPhoto.id) {
                                  InvalidStickerPlaceholder()
                              } else {
                                  StickerPlaceholder()
                                      .task {
                                          await viewModel.loadPhoto(selectedPhoto)
                                      }
                              }
                          }
                          .containerRelativeFrame(.horizontal)
                          .visualEffect { [selection = viewModel.selection] content, proxy in
                              let frame = proxy.frame(in: .scrollView(axis: .horizontal))
                              let distance = min(0, frame.minX)
                              let isLast = selectedPhoto.id == selection.last?.id
                              
                              return content
                                  .hueRotation(.degrees(frame.origin.x / 10))
                                  .scaleEffect(1 + distance / 700)
                                  .offset(x: isLast ? 0 : -distance / 1.25)
                                  .brightness(-distance / 400)
                                  .blur(radius: isLast ? 0 : -distance / 50)
                                  .opacity(isLast ? 1.0 : min(1.0, 1.0 - (-distance / 400)))
                          }
                      }
                  }
              }
              .configureCarousel(
                  viewModel,
                  sheetPresented: $sheetPresented
              )
              .sheet(isPresented: $sheetPresented) {
                  StickerGrid(viewModel: viewModel)
              }
          }
      }
    • 26:15 - Accessing a reference type from a concurrent task

      Task { @concurrent in
          await viewModel.loadPhoto(selectedPhoto)      
      }
    • 29:00 - Processing all photos at once with a task group

      func processAllPhotos() async {
          await withTaskGroup { group in
              for item in selection {
                  guard processedPhotos[item.id] == nil else { continue }
                  group.addTask {
                      let data = await self.getData(for: item)
                      let photo = await PhotoProcessor().process(data: data)
                      return photo.map { ProcessedPhotoResult(id: item.id, processedPhoto: $0) }
                  }
              }
      
              for await result in group {
                  if let result {
                      processedPhotos[result.id] = result.processedPhoto
                  }
              }
          }
      }
    • 30:00 - Kicking off photo processing and configuring the share link in a sticker grid view.

      import SwiftUI
      
      struct StickerGrid: View {
          let viewModel: StickerViewModel
          @State private var finishedLoading: Bool = false
      
          var body: some View {
              NavigationStack {
                  VStack {
                      if finishedLoading {
                          GridContent(viewModel: viewModel)
                      } else {
                          ProgressView()
                              .frame(maxWidth: .infinity, maxHeight: .infinity)
                              .padding()
                      }
                  }
                  .task {
                      await viewModel.processAllPhotos()
                      finishedLoading = true
                  }
                  .toolbar {
                      ToolbarItem(placement: .topBarTrailing) {
                          if finishedLoading {
                              ShareLink("Share", items: viewModel.selection.compactMap {
                                  viewModel.processedPhotos[$0.id]?.sticker
                              }) { sticker in
                                  SharePreview(
                                      "Sticker Preview",
                                      image: sticker,
                                      icon: Image(systemName: "photo")
                                  )
                              }
                          }
                      }
                  }
                  .configureStickerGrid()
              }
          }
      }

Developer Footer

  • Vídeos
  • WWDC25
  • Sessão prática de codificação: aprimore um app usando a concorrência do Swift
  • 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