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
  • Resumo
  • Transcrição
  • Código
  • Otimize o desempenho do SwiftUI com o Instruments

    Descubra o novo instrumento SwiftUI. Abordaremos como o SwiftUI atualiza as visualizações, como as alterações nos dados do seu app afetam essas atualizações e como o novo instrumento ajuda você a visualizar essas causas e efeitos. Para aproveitar ao máximo esta sessão, recomendamos que você se familiarize com o desenvolvimento de apps em SwiftUI.

    Capítulos

    • 0:00 - Introdução
    • 2:19 - Descobrir o instrumento SwiftUI
    • 4:20 - Diagnosticar e corrigir demoras nas atualizações do corpo da visualização
    • 19:54 - Entender as causas e os efeitos das atualizações do SwiftUI
    • 35:01 - Próximas etapas

    Recursos

    • Analyzing the performance of your visionOS app
    • Improving app responsiveness
    • Measuring your app’s power use with Power Profiler
    • Performance and metrics
    • Understanding and improving SwiftUI performance
      • Vídeo HD
      • Vídeo SD

    Vídeos relacionados

    WWDC25

    • Otimize o desempenho da CPU com o Instruments

    WWDC23

    • Analyze hangs with Instruments
    • Demystify SwiftUI performance
    • Explore SwiftUI animation

    WWDC22

    • Compose custom layouts with SwiftUI

    WWDC21

    • Demystify SwiftUI

    Tech Talks

    • Explore UI animation hitches and the render loop
  • Buscar neste vídeo...

    Olá! Eu sou o Jed, da equipe Instruments. E eu sou o Steven, da equipe Apple Music. Apps excelentes têm um desempenho excelente. Qualquer código executado no app pode impactar negativamente o desempenho. É importante analisar o app para identificar quais partes do código podem ser gargalos e, então, resolver esses problemas para que ele funcione da melhor forma possível. Hoje vamos focar em maneiras de identificar quando o código SwiftUI é o gargalo e mostrar como ajudar a SwiftUI a funcionar de modo mais eficiente. Mas, afinal, como saber quando você tem um problema de desempenho? Um sintoma que pode ser notado é que o app fica menos responsivo devido aos travamentos e lentidão. As animações congelam ou pulam, ou a rolagem pode ficar lenta. A melhor maneira de identificar problemas de desempenho no app é usando o Instruments. Hoje, nosso foco é no diagnóstico de problemas de desempenho em códigos que usam SwiftUI. Começaremos com uma introdução ao novo instrumento SwiftUI, incluído no Instruments 26. Também vamos analisar um app com atualizações longas no view body, entender por que elas afetam o desempenho e usar o Instruments para localizá-las e corrigi-las. Por fim, vamos nos aprofundar nas causas e nos efeitos das atualizações da SwiftUI. Usaremos o Instruments para identificar atualizações desnecessárias e mostrar como removê-las. Problemas de desempenho podem ter muitas causas implícitas diferentes, mas hoje vamos focar naqueles relacionados ao uso da SwiftUI.

    Se o problema do app não for no código SwiftUI, confira "Analisar travamentos com o Instruments" e "Otimizar o desempenho da CPU com o Instruments" para começar a investigar o que está acontecendo.

    Steven e eu estamos trabalhando em um app. Steven, você pode mostrar o que fizemos até agora? Valeu, Jed! O app se chama Landmarks e apresenta alguns dos lugares mais incríveis do mundo. Cada ponto turístico mostra a distância até minha localização atual, assim, posso sonhar com o próximo destino, seja pegando um voo para o outro lado do mundo, seja pegando a estrada para uma viagem curta! O app parece ótimo até agora, mas durante os testes, percebi que a rolagem nem sempre é tão suave quanto eu gostaria. Quero entender de vez o que está causando isso. Jed, você comentou sobre o novo instrumento SwiftUI. Que tal fazermos um tour? Claro! Agora o Instruments 26 tem uma nova forma de identificar problemas de desempenho em apps SwiftUI: o instrumento SwiftUI de última geração.

    O modelo SwiftUI atualizado traz instrumentos diferentes que ajudam a avaliar o desempenho do app. Para começar, temos o novo instrumento SwiftUI, sobre o qual falarei mais daqui a pouco. Depois temos o Time Profiler, que revela como o app está trabalhando na CPU ao longo do tempo. E, por fim, temos os instrumentos Hangs e Hitches, que acompanham a capacidade de resposta do app.

    O primeiro passo ao investigar possíveis problemas de desempenho no app é analisar as informações de alto nível fornecidas pelo instrumento SwiftUI.

    A primeira faixa do instrumento SwiftUI é chamada de "Grupos de Atualização" e mostra quando a SwiftUI está trabalhando.

    Se o uso da CPU estiver alto enquanto essa faixa estiver vazia, é sinal de que o problema provavelmente está fora da SwiftUI. As demais faixas do instrumento SwiftUI permitem identificar facilmente atualizações longas da SwiftUI e quando elas ocorrem.

    A faixa Long View Body Updates destaca quando a execução da propriedade "body" está demorando demais. A faixa Atualizações Representáveis Longas mostra atualizações da view e do controle de view que estão demorando muito.

    Por fim, a faixa Outras Atualizações Longas mostra outros tipos de trabalho demorados da SwiftUI. Essas três faixas mostram todas as atualizações longas que podem afetar negativamente o desempenho do app. As atualizações são exibidas em laranja e vermelho conforme a probabilidade de causarem lentidão ou travamento. Se essas atualizações causam mesmo travamentos ou lentidão no app pode depender das condições do dispositivo, mas investigar as atualizações marcadas em vermelho é um ótimo ponto de partida.

    Para começar a usar o instrumento SwiftUI, instale o Xcode 26. Depois, no dispositivo que deseja executar e perfilar o app, atualize o OS para uma versão que inclua suporte para gravação de rastreamentos da SwiftUI. Acho que estamos prontos para criar o perfil do app Landmarks. - Steven, agora é com você! - Valeu, Jed!

    O projeto já está aberto no Xcode. Para começar a criar o perfil, vou pressionar Command+I. O Xcode compila o app no modo de lançamento e abre automaticamente o Instruments.

    No seletor de modelos, escolherei o modelo SwiftUI e clicarei no botão de gravação para iniciar a gravação.

    Vou começar rolando a lista de pontos turísticos. Há uma prateleira horizontal para cada continente.

    Vou rolar até o final da prateleira América do Norte para carregar mais algumas views.

    E, então, vou clicar para parar a gravação.

    Com a gravação interrompida, o Instruments copia os dados de criação de perfil do meu dispositivo e os analisa. Com o processamento concluído, poderei usar o instrumento SwiftUI para verificar se há possíveis problemas de desempenho que precisam da minha atenção. Vou maximizar a janela para facilitar a view de tudo.

    Começarei inspecionando as faixas de atualização longa de alto nível no instrumento SwiftUI.

    View bodies com execuções demoradas resultam em problemas de desempenho na SwiftUI, por isso, inspecionarei primeiro a faixa Long View Body Updates.

    Há algumas atualizações longas em laranja e vermelho nesta faixa, então, quero investigá-las. Vou clicar para expandir a faixa da SwiftUI.

    E isso revela três subfaixas: Atualizações do view body, Atualizações Representáveis e Outras Atualizações. Cada subfaixa tem atualizações longas em laranja e vermelho, assim como as faixas de alto nível. O restante das atualizações é mostrado em cinza. Selecionarei a faixa Atualizações do view body.

    O painel de detalhes mostra um resumo hierárquico dos view bodies que foram executados durante a criação de perfil. Expandindo o processo do meu app na hierarquia, tenho os módulos de todas as atualizações do view body o que foram executadas.

    Posso filtrar por atualizações longas clicando no menu suspenso e selecionando o resumo Long View Body Updates.

    Os números mostram que há várias atualizações longas para investigar.

    Vou clicar para expandir o módulo do meu app. LandmarkListItemView tem várias atualizações longas, então, vou começar com essa view.

    Passar o mouse sobre o nome da view revela uma seta.

    E clicar na seta revela um menu de contexto. Vou escolher "Mostrar Atualizações", que mostra uma lista de todas as atualizações longas desse view body.

    Vou clicar com o botão direito em uma atualização longa e clicar em Set Inspection Range and Zoom. Isso define a seleção no meu rastreamento para o intervalo dessa atualização de view body. Em seguida, clicarei na faixa do instrumento Time Profiler.

    Aqui vejo o que está acontecendo na CPU enquanto o view body está em execução.

    Regularmente, o Time Profiler coleta dados para demonstrar o que está sendo executado na CPU. Em cada amostra, ele verifica a função que está sendo executada e salva a informação na sessão de criação de perfil. O painel de detalhes do perfil abaixo mostra pilhas de chamadas das amostras registradas no rastreamento. Nesse caso, estas são as amostras registradas durante a execução do meu view body.

    Seguro a tecla Option e clico para expandir a pilha de chamadas do thread principal.

    A SwiftUI apresenta uma pilha de chamadas muito profunda. O que mais me interessa aqui é LandmarkListItemView. Pressionarei Command+F para pesquisar a pilha e digitarei o nome no campo de busca.

    Aí está o meu view body. Na coluna à extrema esquerda, Time Profiler mostra o tempo gasto em cada frame na pilha de chamadas.

    A coluna mostra que grande parte do tempo gasto no view body foi na propriedade calculada distance. Em distance, os dois frames mais pesados são chamadas para dois formatters diferentes.

    Este measurementFormatter e este numberFormatter. Vamos voltar ao Xcode para conferir o que está acontecendo no código.

    Isso é LandmarkListItemView, a view de cada ponto turístico na lista.

    E esta é a propriedade distance que notei no Time Profiler. Essa propriedade converte a distância do ponto turístico em uma string formatada para exibir na view.

    Este é o numberFormatter, que o Time Profiler me mostrou que era caro criar.

    E é aqui que o measurementFormatter cria a string, o que também contribuiu bastante para o tempo gasto no view body.

    No view body, estou lendo a propriedade distance para criar o texto do rótulo. Como esses view bodies são executados no thread principal, meu app precisa aguardar a formatação do texto de distância para continuar atualizando a interface.

    Mas por que isso é relevante? Um milissegundo para executar um view body pode não parecer muito, mas o tempo total gasto pode ser significativo, especialmente quando a SwiftUI tem muitas views para atualizar. Ei Jed, como posso encarar o tempo que a SwiftUI leva para executar os view bodies? Essa é uma ótima pergunta. Começarei descrevendo como o loop de renderização funciona nas plataformas da Apple. A cada frame, o app é acionado para lidar com eventos, como toques ou pressionamentos de teclas. Em seguida, ele atualiza a interface. Isso inclui executar a propriedade body de qualquer view da SwiftUI que tenha sido modificada. Tudo isso deve terminar antes do tempo limite de cada frame. O app repassa o trabalho para o sistema, que renderiza as views antes do tempo limite do próximo frame. A saída renderizada finalmente aparece na tela logo ao fim desse prazo. Tudo está funcionando exatamente como deveria. As atualizações são concluídas antes do tempo limite de cada frame, dando ao sistema tempo suficiente para renderizar cada frame e exibi-lo na tela. Agora, vamos comparar com um app que travou devido a um view body que demorou muito.

    Assim como antes, tratamos dos eventos primeiro. Em seguida, executamos as atualizações da interface. Mas no primeiro frame, uma das atualizações da interface demorou demais. Isso fez com que a parte da atualização da interface ultrapassasse o tempo limite do frame. Isso significa que a próxima atualização só pode começar no frame seguinte. E esse frame não está pronto para repassar nada ao renderizador dentro do prazo. Assim, o frame anterior continua visível na tela até que o sistema termine de renderizar o próximo. Travamento é o nome de um frame que permanece visível na tela por tempo demais, atrasando outros frames. Os travamentos atrapalham a fluidez das animações. Para saber mais sobre travamentos, confira o artigo "Noções básicas de travamentos no seu app" e esta Tech Talk, que explicam o funcionamento do loop de renderização e como corrigir diferentes tipos de travamento. Steven, isso ajuda a explicar por que o tempo de execução do view body é importante? Sim, isso foi realmente útil! Então, as atualizações do view body que demoram mais podem fazer com que meu app ultrapasse o tempo limite do frame, o que causa travamentos. Então, preciso calcular a string de distância de cada ponto turístico e armazená-la em cache antes da view, e não fazê-lo enquanto o body está em execução. Vamos voltar ao código.

    Ok, essa é a propriedade distance que é executada toda vez que o view body é atualizado. Em vez de fazer esse trabalho durante a execução do view body, vou movê-lo para um lugar mais centralizado, a classe em que gerencio as atualizações de localização.

    A classe LocationFinder é responsável por receber atualizações sempre que minha localização muda. Em vez de calcular a string de distância formatada no view body, posso criá-la antes e armazená-la em cache, assim ela já estará pronta sempre que uma das minhas views precisar exibi-la.

    Começarei atualizando o inicializador para criar os formatters que antes estavam sendo criados no view body.

    Adicionei essa propriedade chamada formatter para armazenar meu measurementFormatter.

    E no início do inicializador, estou criando o numberFormatter que antes era criado na minha view.

    E o measurementFormatter, que estou armazenando na nova propriedade que adicionei. Como o formato nunca muda, posso reutilizar os formatters sempre que for necessário atualizar as strings de distância, evitando o custo de recriar um formatter a cada execução do view body. Depois vou precisar manter as strings em cache para que minhas views possam usá-las quando necessário. Vou adicionar código para gerenciar essas atualizações.

    Tenho uma matriz para armazenar os pontos turísticos, que usarei para calcular as distâncias.

    Também tenho um dicionário para armazenar em cache as strings de distância depois de calculadas.

    Essa função chamada updateDistances recalculará as strings sempre que minha localização mudar.

    Estou usando o formatter aqui para criar o texto de distância.

    E armazenando o texto em cache.

    Daqui a pouco, vou chamar essa última função na minha view para obter o texto em cache.

    Só mais um passo para eu terminar aqui. Quando minha localização for atualizada, precisarei atualizar o cache de strings.

    Vou clicar no menu suspenso da barra de salto e pular para a função didUpdateLocations, que CoreLocation chama quando minha localização muda.

    Aqui, chamo a função updateDistances que criei.

    Agora, voltarei à minha view.

    E atualizarei a view para usar o valor em cache.

    Essas alterações devem corrigir as atualizações lentas do view body. Agora, vamos examinar um rastreamento de Instruments feito com essas correções implementadas, para verificar se houve melhoria.

    Com a faixa Atualizações do View Body selecionada, o resumo de Long View Body Updates mostra que as atualizações longas em LandmarkListItemView desapareceram.

    Ainda há duas atualizações longas do view body no resumo, mas observe que elas acontecem logo no início do rastreamento, enquanto o app se prepara para renderizar o primeiro frame. É comum que as atualizações logo após a inicialização do app demorem mais, já que o sistema ainda está criando a hierarquia inicial de views. Mas isso não resultará em um travamento. O importante é que as atualizações longas de LandmarkListItemView, que poderiam causar travamentos durante a rolagem, foram corrigidas e desapareceram da lista. Assim, posso ter certeza de que não estou desacelerando a SwiftUI enquanto renderiza todas as minhas views na tela.

    Corrigir as atualizações longas do view body melhora bastante o desempenho de um app. No entanto, muitas atualizações desnecessárias no view body também podem causar problemas de desempenho. Vamos entender o porquê.

    Este é o diagrama que Jed mostrou antes. Mas, desta vez, não houve uma atualização que demorou mais do que outras. Mas há um grande número de atualizações relativamente rápidas que precisam acontecer durante esse frame.

    Todo esse trabalho extra faz com que o app ultrapasse o tempo limite para enviar o frame. E, novamente, a próxima atualização é atrasada por um frame. E como não há nada a ser repassado ao renderizador, mais uma vez temos um travamento, pois o frame anterior permanece visível por dois frames inteiros. Mencionei o possível impacto de desempenho causado por atualizações desnecessárias nas views porque estou trabalhando em um novo recurso para o app, onde acho que isso vai ser bastante relevante. Rolar pelos pontos turísticos me deu uma vontade enorme de explorar novos lugares, mas é muito difícil decidir por onde começar. Então tive uma ideia para facilitar isso. Vou mostrar a você. Inclui um novo botão de coração em cada ponto turístico, que posso tocar para adicionar ou remover dos favoritos.

    Vou mostrar o código.

    Em LandmarkListItemView, adicionei essa overlay que exibe meu novo botão de coração.

    A ação do botão chama a função toggleFavorite na minha classe de dados do modelo para favoritar ou desfavoritar o ponto turístico.

    O ícone mostra um coração preenchido se o ponto estiver favoritado, ou um coração vazio, se não estiver.

    Pressiono Command e clico em toggleFavorite para pular para essa função.

    E é assim que estou adicionando um favorito.

    O modelo armazena vários pontos turísticos favoritos. E estou adicionando o ponto turístico à matriz quando ele é marcado como favorito.

    Para remover um favorito, faço o contrário.

    E é isso que tenho até o momento. Sei que o recurso ainda precisa de ajustes, mas é uma boa prática usar o Instruments desde o início e muitas vezes durante o desenvolvimento. Então, vamos descobrir como meu novo recurso está se saindo. Vou pressionar Command+I para criar o app, voltar ao Instruments e clicar em gravar novamente.

    Acho que vou rolar até a lista da América do Norte como antes, ir para a direita e tocar no coração para favoritar Muir Woods. Porque não é tão longe de onde eu moro, mas de qualquer forma ainda não estive lá! Beleza, agora vou subir a tela de novo. E vou favoritar um lugar mais distante... que tal o Monte Fuji? Essa seria uma aventura divertida. Agora vou parar a gravação.

    Quero garantir que, ao tocar no meu novo botão de favoritos, não estejam ocorrendo atualizações desnecessárias. Ter uma expectativa ao analisar um rastreamento e procurar o que foge do padrão pode ser uma ótima maneira de identificar possíveis problemas.

    Vou expandir a faixa do instrumento SwiftUI e selecionar a subfaixa Atualizações do view body.

    Como toquei nos favoritos Muir Woods e Mount Fuji, espero que só essas duas views tenham sido atualizadas. Toquei nos botões na segunda metade do rastreamento, depois de rolar para baixo. Vou destacar essa parte do rastreamento para focar apenas no trecho que me interessa.

    Agora vou conferir o painel de detalhes abaixo. Expandirei a hierarquia para encontrar a lista de atualizações das minhas views.

    É uma supresa descobrir que LandmarkListItemView na verdade foi atualizado várias vezes. Mas por quê? Ao depurar uma atualização de view em um app UIKit, coloco um ponto de interrupção no código e inspeciono o backtrace para tentar descobrir por que a view foi atualizada. Mas nos meus apps SwiftUI, como o Landmarks, isso não funcionou bem. As pilhas de chamadas da SwiftUI parecem mais difíceis de entender. Jed, por que essa abordagem não funciona com apps da SwiftUI? Deixe-me explicar. O Xcode ajuda a entender a causa e o efeito em código imperativo, como nos apps UIKit, mostrando os backtraces quando você aciona um ponto de interrupção. O UIKit é um framework imperativo, portanto, os backtraces são úteis para depurar causa e efeito. Aqui, posso dizer que meu rótulo está sendo atualizado porque defini a propriedade isOn em viewDidLoad. E, deduzindo pelos nomes de alguns frames do sistema no backtrace, parece que isso aconteceu enquanto meu app estava iniciando sua primeira cena. Quando comparo com um app SwiftUI semelhante, encontro várias atualizações recursivas em componentes internos da SwiftUI, separadas por frames dentro de algo chamado AttributeGraph. Nada disso me diz por que exatamente a minha view precisa ser atualizada.

    Como a SwiftUI é declarativa, o backtrace não explica por que a view está sendo atualizada. Então, como entender o que está causando a atualização das views na SwiftUI? Primeiro, você precisará entender como SwiftUI funciona.

    Vou dar um pequeno exemplo para mostrar como o modelo de dados da SwiftUI, o AttributeGraph, define dependências entre as views e evita reexecutar sua view quando não há necessidade. Não abordarei todos os detalhes hoje, mas esta seção deve dar uma base para entender como as atualizações fluem no app.

    As views declaram conformidade com o protocolo View. Depois implementam a propriedade body para definir aparência e comportamento, retornando outro valor de View.

    Aqui, OnOffView retorna uma view Text de seu body, passando um rótulo que muda de acordo com o valor da variável de estado isOn.

    Quando essa view é adicionada pela primeira vez à hierarquia, a SwiftUI recebe da view pai um objeto chamado atributo, que armazena a estrutura da view. As estruturas da view são recriadas com frequência, mas os atributos mantêm a identidade e o estado por todo o ciclo de vida da view. Portanto, à medida que a view pai é atualizada, o valor desse atributo muda, mas a identidade, não. A view é solicitada a criar os próprios atributos para armazenar seu estado e definir seu comportamento. Ela cria primeiro um armazenamento para a variável de estado isOn, e um atributo que rastreia quando essa variável de estado muda. Em seguida, a view cria um atributo para executar seu view, que depende de ambos. Sempre que o atributo do view body precisa gerar um novo valor, ele lê o valor atual da view passada pela view pai. Depois o atributo atualiza uma cópia da estrutura da view com o valor atual da variável de estado. Em seguida, ele acessa a propriedade calculada "body" nessa cópia temporária da view e salva o valor retornado como o novo valor do atributo. Como o body retornou uma view Text, a SwiftUI configura os atributos necessários para exibir esse texto.

    A view Text cria um atributo que depende do ambiente para acessar os estilos padrão atuais, como cor do primeiro plano e fonte, determinando como o texto renderizado deve aparecer. Esse atributo adiciona uma dependência ao view body para acessar a string que será renderizada da estrutura Text retornada. Por fim, Text cria outro atributo que constrói uma descrição do que deve ser renderizado com base no texto estilizado.

    Agora, vamos falar sobre o que acontece quando você altera uma variável de estado. Ao fazer isso, a SwiftUI não atualiza as views imediatamente. Em vez disso, cria outra transação. Um transação é uma alteração na hierarquia de view da SwiftUI que precisa ser feita antes do próximo frame.

    Essa transação marcará o atributo signal para a variável de estado como desatualizado. Ao se preparar para atualizar o próximo frame, a SwiftUI executa a transação e aplica a atualização que foi agendada. Agora que um atributo foi marcado como desatualizado, a SwiftUI percorre a cadeia de atributos que dependem dele, marcando cada um como desatualizado definindo um sinalizador. A definição do sinalizador é bem rápida, e nenhum trabalho extra acontece nesse momento. Após a execução de outras transações, a SwiftUI precisa descobrir o que desenhar na tela para esse frame. Mas ele não consegue acessar essa informação porque ela está marcada como desatualizada. Por isso, a SwiftUI precisa atualizar todas as dependências dessa informação para decidir o que desenhar.

    Começando pelas que não têm dependências desatualizadas, como o sinal Estado. Agora, o atributo do view body pode ser atualizado. Ele é executado novamente, produzindo um novo valor de estrutura Text com uma string atualizada. Isso é passado ao atributo de estilo Apply existente, e as atualizações continuam até que todos os atributos necessários para determinar o que precisa ser desenhado tenham sido atualizados. Agora a SwiftUI é capaz de responder à pergunta "o que ele deve desenhar na tela?"

    Quando pergunto "por que o view body foi executado", quero perguntar "o que marcou o view body como desatualizado?". Geralmente, você pode controlar quando dependências marcam seu view body como desatualizado, especialmente se forem suas. Mas a SwiftUI também executa um trabalho extra para mostrar a view. Embora esse trabalho seja necessário e geralmente inevitável, entender quando acontece pode ser valioso. Disponibilizar informações sobre as causas e os efeitos das atualizações da view é um grande recurso do novo instrumento SwiftUI. O Gráfico de Causa e Efeito registra todas essas relações de causa e efeito e as exibe em um gráfico com esta aparência.

    Começamos com a atualização do view body que estamos investigando. A atualização aparece como um nó identificada como uma atualização do view body e um título informando a qual tipo de view ela corresponde.

    Há uma seta apontando para ela de um nó que representa a mudança de Estado. A seta, rotulada como "atualização", indica que a mudança de estado atualizou a view. As arestas rotuladas como "Criação" indicam o que fez sua view aparecer pela primeira vez na hierarquia de view. O título do nó da mudança de estado informa o nome da variável de estado e o tipo de view ao qual ela está vinculada. Ao selecionar a mudança de estado, será exibido um backtrace de onde o valor foi atualizado.

    Continuando à esquerda do gráfico de causa e efeito, é possível ver que a mudança de estado ocorreu devido a um movimento, como o toque em um botão.

    Steven, o que o gráfico de causa mostra para o app Landmarks? Vamos analisar o gráfico de Causa e Efeito para saber por que aconteceram todas essas atualizações extras no view body. Esta é a view do Gráfico de Causa e Efeito. O nó para LandmarkListItemView.body está selecionado. Os nós azuis no gráfico representam partes do meu próprio código ou ações que executei ao interagir com o app. O gráfico mostra a cadeia de causas e efeitos da esquerda para a direita.

    O nó "Gesture" representa meus toques no botão de favorito.

    Isso fez com que a matriz de pontos turísticos favoritos fosse atualizada, o que causou a atualização de LandmarkListItemView várias vezes. É muito mais do que eu esperava.

    Parece que tocar em um único botão de favorito faz com que muitas views de itens na tela sejam atualizadas, e não apenas a que eu toquei. Então, vamos descobrir o que está acontecendo aqui voltando ao código.

    Voltarei para LandmarkListItemView. A forma como estou verificando se um ponto turístico está marcado como favorito é chamando modelData.isFavorite e passando o ponto turístico. ModelData é meu objeto de modelo de nível superior, que usa a macro @Observable para que a SwiftUI atualize minha view conforme suas propriedades mudam. Pressiono Command e clico em isFavorite para pular para essa função.

    Aqui, estou acessando a matriz favoritesCollection.landmarks para verificar se esse ponto é um favorito. Isso faz com que @Observable estabeleça uma dependência entre cada view de item e toda a matriz de favoritos. Assim, sempre que adiciono um favorito, o view body de cada item é executado, pois a matriz foi alterada. Vou mostrar como isso funciona.

    Esses são alguns dos meus LandmarkListItemViews, e minha classe ModelData com favoritesCollection, que rastreia meus pontos turísticos favoritos. Atualmente, meu único favorito é o ponto turístico número dois. A classe ModelData tem uma função isFavorite, e cada LandmarkListItemView chama essa função para determinar se o ícone deve ser destacado ou não.

    A função isFavorite verifica se o ponto turístico está contido na coleção, e cada view renderiza o próprio botão. Como cada view acessou a matriz de favoritos, mesmo que indiretamente, a macro @Observable criou uma dependência para cada view em toda a matriz de favoritos.

    E o que acontece quando quero incluir um novo favorito tocando no botão de favorito em outra view? A view chama toggleFavorite, que adiciona um novo ponto turístico aos meus favoritos. Como todos os meus LandmarkListItemViews dependem de favoritesCollection, as outras views são marcadas como desatualizadas e seus bodies executados novamente.

    Mas isso não é o ideal, porque a única view que realmente mudei foi a número três. O que preciso é que as dependências de dados da minha view sejam mais granulares, para que apenas os bodies necessários sejam atualizados quando os dados mudarem.

    Então, vamos repensar um pouco isso. Sei que cada uma das views tem um ponto turístico com seu status de favorito, esteja favoritado ou não.

    Então, para acompanhar esse status, criarei um modelo de view Observable para minha view. O modelo tem uma propriedade isFavorite para rastrear o status de favorito, e cada view terá seu próprio modelo.

    Agora posso armazenar meus modelos de view na classe ModelData. Cada view pode obter seu próprio modelo e alternar o status de favorito conforme necessário. Então, em vez de cada view depender da matriz completa de favoritos, cada uma depende apenas do modelo de view do seu próprio ponto turístico. Vamos adicionar mais um favorito! Tocar no botão chama toggleFavorite, que atualiza o modelo de view para a view número um. Como a view um depende apenas de seu próprio modelo, é a única view cujo body é executado novamente. Vamos descobrir como essas mudanças se saíram no Landmarks.

    Este é um rastreamento que gravei após a implementação das melhorias no novo modelo de view. Vou clicar na subfaixa View Body Updates novamente. Vou selecionar a mesma parte da linha do tempo de antes.

    No painel de detalhes, expandirei o processo e o módulo Landmarks.

    Agora, há apenas duas atualizações. Já que mudei dois favoritos, vamos verificar o gráfico para garantir que está tudo certo. Passo o mouse sobre o nome da view, clico na seta e seleciono "Show Cause & Effect Graph".

    E aqui está o gráfico novamente.

    Agora, a seta do nó @Observable para meu view body mostra apenas duas atualizações, uma para cada botão. Ao substituir a dependência da view de cada item em toda a matriz de favoritos por um modelo de view fortemente acoplado, reduzi as atualizações desnecessárias do view body, ajudando a manter meu app mais fluido. Neste caso, o gráfico era relativamente pequeno, pois as causas das atualizações eram bastante limitadas. No entanto, o gráfico pode ser muito maior quando há mais causas distintas. Isso acontece, por exemplo, quando a view lê dados em Environment. Jed, você pode nos dar um exemplo? Claro! Começarei falando sobre como o ambiente funciona. Os valores no ambiente são armazenados na estrutura EnvironmentValues, que é um tipo de valor, semelhante a um dicionário. Cada uma dessas views depende da estrutura EnvironmentValues, pois cada view acessa o ambiente usando o wrapper da propriedade environment. Quando qualquer valor no ambiente é atualizado, cada view com uma dependência do ambiente é notificada de que seu body pode precisar ser executado. Em seguida, cada uma dessas views verifica se o valor que está lendo foi alterado. Se o valor mudou, o view body precisará ser executado novamente. Se não mudou, a SwiftUI poderá pular a execução do view body porque ele já está atualizado. Vamos explorar como essas atualizações aparecem no Gráfico de Causa e Efeito.

    Há dois tipos principais de nó no gráfico que representam atualizações no ambiente. As atualizações do External Environment incluem itens no nível do app, como o esquema de cores, que são atualizados fora da SwiftUI. As atualizações de EnvironmentWriter representam alterações em um valor do ambiente que acontecem na SwiftUI. As atualizações feitas no app usando o modificador dot-environment entram nessa categoria. Digamos que o valor do esquema de cores no ambiente seja atualizado porque o dispositivo mudou para o modo escuro. Como isso apareceria no Gráfico de Causa e Efeito para essas views? O gráfico mostrará um nó de "External Environment" para View1, já que o esquema de cores é uma atualização de ambiente no nível do sistema. E também mostrará um nó indicando que o body de View1 foi executado. Como View2 também lê o ambiente, ela tem uma atualização de External Environment no gráfico como causa. Mas View2 não lê o valor do esquema de cores, então, seu body não é executado. No gráfico, uma atualização de view cujo body não foi executado é representada por um ícone esmaecido. Nesse caso, esses dois nós de External Environment representam a mesma atualização. Se você passar o mouse ou clicar em um dos nós para a mesma atualização, ambos serão destacados ao mesmo tempo para facilitar a identificação. Ambas as atualizações da view são mostradas no gráfico, pois mesmo que o view body não precise ser executado após uma atualização do ambiente, ainda há um custo associado à verificação de atualizações no valor de interesse da view. O tempo gasto pode aumentar rapidamente se o app tiver muitas views lendo dados do ambiente. Por isso é importante evitar armazenar valores que são atualizados com muita frequência, como valores de geometria ou timers, no ambiente. E este é o Gráfico de Causa e Efeito. É uma ótima maneira de visualizar como os dados fluem pelo app, ajudando a garantir que as views não sejam atualizadas mais do que o necessário. Nesta sessão, abordamos algumas práticas recomendadas para atingir um ótimo desempenho no app SwiftUI. É importante manter a agilidade dos view bodies para que a SwiftUI tenha tempo de colocar a interface na tela sem atrasos. Atualizações desnecessárias no body podem mesmo atrapalhar. Projete o fluxo de dados para atualizar as views apenas quando necessário e tenha cuidado extra com dependências que mudam frequentemente. Por fim, lembre-se de usar o Instruments no início e com frequência durante o desenvolvimento para analisar o desempenho do app. Sei que entreguei muito conteúdo hoje. Mas o mais importante é se certificar de que os view bodies sejam atualizados rapidamente e só quando necessário para um ótimo desempenho da SwiftUI. Use a SwiftUI para verificar o desempenho do app ao longo do caminho.

    Na sessão de hoje, mostramos como criar o perfil dos apps com a SwiftUI, mas ainda há muito o que explorar. Confira a documentação vinculada na descrição do vídeo para conhecer outros recursos do instrumento. Também adicionamos links para mais vídeos e material de referência sobre como analisar e melhorar o desempenho do app. Agradecemos por estar conosco! Estamos na torcida para que você obtenha o melhor desempenho dos seus apps com a nova SwiftUI.

    • 8:47 - LandmarkListItemView

      import SwiftUI
      import CoreLocation
      
      /// A view that shows a single landmark in a list.
      struct LandmarkListItemView: View {
          @Environment(ModelData.self) private var modelData
      
          let landmark: Landmark
      
          var body: some View {
              Image(landmark.thumbnailImageName)
                  .resizable()
                  .aspectRatio(contentMode: .fill)
                  .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
                  .overlay { ... }
                  .clipped()
                  .cornerRadius(Constants.cornerRadius)
                  .overlay(alignment: .bottom) {
                      VStack(spacing: 6) {
                          Text(landmark.name)
                              .font(.title3).fontWeight(.semibold)
                              .multilineTextAlignment(.center)
                              .foregroundColor(.white)
      
                          if let distance {
                              Text(distance)
                                  .font(.callout)
                                  .foregroundStyle(.white.opacity(0.9))
                                  .padding(.bottom)
                          }
                      }
                  }
                  .contextMenu { ... }
          }
      
          private var distance: String? {
              guard let currentLocation = modelData.locationFinder.currentLocation else { return nil }
              let distance = currentLocation.distance(from: landmark.clLocation)
      
              let numberFormatter = NumberFormatter()
              numberFormatter.numberStyle = .decimal
              numberFormatter.maximumFractionDigits = 0
      
              let formatter = MeasurementFormatter()
              formatter.locale = Locale.current
              formatter.unitStyle = .medium
              formatter.unitOptions = .naturalScale
              formatter.numberFormatter = numberFormatter
              return formatter.string(from: Measurement(value: distance, unit: UnitLength.meters))
          }
      }
    • 12:13 - LocationFinder Class with Cached Distance Strings

      import CoreLocation
      
      /// A class the app uses to find the current location.
      @Observable
      class LocationFinder: NSObject {
          var currentLocation: CLLocation?
          private let currentLocationManager: CLLocationManager = CLLocationManager()
      
          private let formatter: MeasurementFormatter
      
          override init() {
              // Format the numeric distance
              let numberFormatter = NumberFormatter()
              numberFormatter.numberStyle = .decimal
              numberFormatter.maximumFractionDigits = 0
      
              // Format the measurement based on the current locale
              let formatter = MeasurementFormatter()
              formatter.locale = Locale.current
              formatter.unitStyle = .medium
              formatter.unitOptions = .naturalScale
              formatter.numberFormatter = numberFormatter
              self.formatter = formatter
      
              super.init()
              
              currentLocationManager.desiredAccuracy = kCLLocationAccuracyKilometer
              currentLocationManager.delegate = self
          }
      
          // MARK: - Landmark Distance
      
          var landmarks: [Landmark] = [] {
              didSet {
                  updateDistances()
              }
          }
      
          private var distanceCache: [Landmark.ID: String] = [:]
      
          private func updateDistances() {
              guard let currentLocation else { return }
      
              // Populate the cache with each formatted distance string
              self.distanceCache = landmarks.reduce(into: [:]) { result, landmark in
                  let distance = self.formatter.string(
                      from: Measurement(
                          value: currentLocation.distance(from: landmark.clLocation),
                          unit: UnitLength.meters
                      )
                  )
                  result[landmark.id] = distance
              }
          }
      
          // Call this function from the view to access the cached value
          func distance(from landmark: Landmark) -> String? {
              distanceCache[landmark.id]
          }
      }
      
      extension LocationFinder: CLLocationManagerDelegate {
          func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
              switch currentLocationManager.authorizationStatus {
              case .authorizedWhenInUse, .authorizedAlways:
                  currentLocationManager.requestLocation()
              case .notDetermined:
                  currentLocationManager.requestWhenInUseAuthorization()
              default:
                  currentLocationManager.stopUpdatingLocation()
              }
          }
          
          func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
              print("Found a location.")
              currentLocation = locations.last
              // Update the distance strings when the location changes
              updateDistances() 
          }
          
          func locationManager(_ manager: CLLocationManager, didFailWithError error: any Error) {
              print("Received an error while trying to find a location: \(error.localizedDescription).")
              currentLocationManager.stopUpdatingLocation()
          }
      }
    • 16:51 - LandmarkListItemView with Favorite Button

      import SwiftUI
      import CoreLocation
      
      /// A view that shows a single landmark in a list.
      struct LandmarkListItemView: View {
          @Environment(ModelData.self) private var modelData
      
          let landmark: Landmark
      
          var body: some View {
              Image(landmark.thumbnailImageName)
                  .resizable()
                  .aspectRatio(contentMode: .fill)
                  .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
                  .overlay { ... }
                  .clipped()
                  .cornerRadius(Constants.cornerRadius)
                  .overlay(alignment: .bottom) { ... }
                  .contextMenu { ... }
                  .overlay(alignment: .topTrailing) {
                      let isFavorite = modelData.isFavorite(landmark)
                      Button {
                          modelData.toggleFavorite(landmark)
                      } label: {
                          Label {
                              Text(isFavorite ? "Remove Favorite" : "Add Favorite")
                          } icon: {
                              Image(systemName: "heart")
                                  .symbolVariant(isFavorite ? .fill : .none)
                                  .contentTransition(.symbolEffect)
                                  .font(.title)
                                  .foregroundStyle(.background)
                                  .shadow(color: .primary.opacity(0.25), radius: 2, x: 0, y: 0)
                          }
                      }
                      .labelStyle(.iconOnly)
                      .padding()
                  }
          }
      }
    • 17:20 - ModelData Class

      /// A structure that defines a collection of landmarks.
      @Observable
      class LandmarkCollection: Identifiable {
          // ...
          var landmarks: [Landmark] = []
          // ...
      }
      
      /// A class the app uses to store and manage model data.
      @Observable @MainActor
      class ModelData {
          // ...
          var favoritesCollection: LandmarkCollection!
          // ...
      
          func isFavorite(_ landmark: Landmark) -> Bool {
              var isFavorite: Bool = false
              
              if favoritesCollection.landmarks.firstIndex(of: landmark) != nil {
                  isFavorite = true
              }
              
              return isFavorite
          }
      
          func toggleFavorite(_ landmark: Landmark) {
              if isFavorite(landmark) {
                  removeFavorite(landmark)
              } else {
                  addFavorite(landmark)
              }
          }
      
          func addFavorite(_ landmark: Landmark) {
              favoritesCollection.landmarks.append(landmark)
          }
      
          func removeFavorite(_ landmark: Landmark) {
              if let landmarkIndex = favoritesCollection.landmarks.firstIndex(of: landmark) {
                  favoritesCollection.landmarks.remove(at: landmarkIndex)
              }
          }
          // ...
      }
    • 20:50 - OnOffView

      struct OnOffView: View {
          @State private var isOn = true
          var body: some View {
              Text(isOn ? "On" : "Off")
          }
      }
    • 29:21 - Favorites View Model Class

      @Observable class ViewModel {
          var isFavorite: Bool
          
          init(isFavorite: Bool = false) {
              self.isFavorite = isFavorite
          }
      }
    • 29:21 - ModelData Class with New ViewModel

      @Observable @MainActor
      class ModelData {
          // ...
          var favoritesCollection: LandmarkCollection!
          // ...
      
          @Observable class ViewModel {
              var isFavorite: Bool
              init(isFavorite: Bool = false) {
                  self.isFavorite = isFavorite
              }
          }
      
          // Don't observe this property because we only need to react to changes
          // to each view model individually, rather than the whole dictionary
          @ObservationIgnored private var viewModels: [Landmark.ID: ViewModel] = [:]
      
          private func viewModel(for landmark: Landmark) -> ViewModel {
              // Create a new view model for a landmark on first access
              if viewModels[landmark.id] == nil {
                  viewModels[landmark.id] = ViewModel()
              }
              return viewModels[landmark.id]!
          }
      
          func isFavorite(_ landmark: Landmark) -> Bool {
              // When a SwiftUI view, such as LandmarkListItemView, calls
              // `isFavorite` from its body, accessing `isFavorite` on the 
              // view model here establishes a direct dependency between
              // the view and the view model
              viewModel(for: landmark).isFavorite
          }
      
          func toggleFavorite(_ landmark: Landmark) {
              if isFavorite(landmark) {
                  removeFavorite(landmark)
              } else {
                  addFavorite(landmark)
              }
          }
      
          func addFavorite(_ landmark: Landmark) {
              favoritesCollection.landmarks.append(landmark)
              viewModel(for: landmark).isFavorite = true
          }
      
          func removeFavorite(_ landmark: Landmark) {
              if let landmarkIndex = favoritesCollection.landmarks.firstIndex(of: landmark) {
                  favoritesCollection.landmarks.remove(at: landmarkIndex)
              }
              viewModel(for: landmark).isFavorite = false
          }
          // ...
      }
    • 31:34 - Cause and effect: EnvironmentValues

      struct View1: View {
          @Environment(\.colorScheme)
          private var colorScheme
      
          var body: some View {
              Text(colorScheme == .dark
                      ? "Dark Mode"
                      : "Light Mode")
          }
      }
      
      struct View2: View {
          @Environment(\.counter) private var counter
      
          var body: some View {
              Text("\(counter)")
          }
      }
    • 0:00 - Introdução
    • Saiba como otimizar o desempenho de apps SwiftUI com o novo SwiftUI e o modelo no Instruments 26. Use o Instruments para criar perfis dos apps e identificar gargalos, como atualizações longas de corpo de visualizações e atualizações desnecessárias do SwiftUI, que podem causar travamentos, pausas em animações e rolagem atrasada. O app de exemplo apresentado, Landmarks, exibe pontos turísticos globais e suas distâncias em relação à localização do usuário. Veja como usar o novo SwiftUI para melhorar a rolagem do app, diagnosticando e resolvendo problemas de desempenho no código SwiftUI.

    • 2:19 - Descobrir o instrumento SwiftUI
    • O Instruments 26 apresenta um novo SwiftUI e o modelo para criação de perfil de apps SwiftUI. Similar aos instrumentos Time Profiler e Hangs and Hitches, ele ajuda a identificar problemas de desempenho. A faixa Update Groups exibe o trabalho do SwiftUI. As três faixas destacam atualizações longas do corpo da visualização, atualizações longas dos representáveis e outras atualizações: laranja e vermelho com base na probabilidade de causarem travamentos. Para usar o novo SwiftUI, instale o Xcode 26 e atualize o sistema operacional do dispositivo para a versão mais recente.

    • 4:20 - Diagnosticar e corrigir demoras nas atualizações do corpo da visualização
    • O exemplo usa o Xcode 26 e o Instruments 26 para criar o perfil do app Landmarks, que é escrito em SwiftUI. Comece abrindo o Instruments e selecionando o modelo SwiftUI para registrar o desempenho do app. Depois, interaja com o app no iPhone, rolando pela lista de pontos turísticos, o que carrega visualizações adicionais. Após a gravação ser interrompida, o Instruments processa os dados, e você pode analisar a trilha do SwiftUI. Foque a trilha Long View Body Updates, para identificar visualizações, como 'LandmarkListItemView', que causam problemas de desempenho. Ao expandir a trilha SwiftUI e usar o instrumento Time Profiler, analise o uso da CPU durante as atualizações do corpo da visualização. Certas propriedades computadas, como formatadores usados para converter e exibir dados de distância, podem estar consumindo tempo excessivo. Otimize o tempo de execução do corpo da visualização no SwiftUI, pois eles são executados na thread principal. Atrasos podem fazer o app perder os prazos dos quadros, resultando em travamentos. Interrupções fazem com que as animações pareçam menos fluidas e podem impactar a experiência do usuário. Para resolver problemas de desempenho no projeto de exemplo, calcule e armazene em cache a string de distância com antecedência, em vez de realizar esses cálculos durante a atualização do corpo da visualização, garantindo um desempenho responsivo do app. No Xcode, há um processo de otimização na classe 'LocationFinder', que gerencia as atualizações de localização. O sistema calculava as strings de distância formatadas dentro do corpo da visualização de 'LandmarkListItemView', resultando em atualizações ineficientes. Para resolver isso, a lógica foi movida para a classe 'LocationFinder'. Aqui, o sistema cria e armazena os formatadores no inicializador para serem reutilizados, evitando a criação redundante. Um dicionário armazena em cache as strings de distância após o cálculo. A função 'updateDistances' é responsável por recalcular essas strings sempre que a localização mudar. Essa função utiliza os formatadores criados anteriormente para gerar a string de distância e armazená-la no cache. O framework CoreLocation chama o método 'locationManager(_:didUpdateLocations:)' no objeto 'CLLocationManagerDelegate' quando a localização do dispositivo é alterada. Ao chamar 'updateDistances' dentro desse método, o cache é mantido atualizado. As visualizações recuperam as strings de distância em cache, eliminando o recálculo durante as atualizações do corpo da visualização. A seguir, adicione um novo recurso: um botão de coração para favoritar pontos turísticos. Quando alguém toca no botão, a função 'toggleFavorite' é chamada, atualizando a classe de dados do modelo para adicionar ou remover o ponto turístico da lista de favoritos. A visualização reflete essa mudança exibindo um ícone de coração preenchido ou vazio. Ao criar o perfil do novo recurso no app usando o Instruments, a 'LandmarkListItemView' foi atualizada com mais frequência do que o esperado. Esse comportamento inesperado leva à investigação da lógica de atualização da visualização, evidenciando os desafios de depurar atualizações no SwiftUI em comparação com apps UIKit, em que a inspeção com breakpoints não é tão direta para frameworks declarativos.

    • 19:54 - Entender as causas e os efeitos das atualizações do SwiftUI
    • No Xcode, depurar código imperativo, como em apps UIKit, é simples usando backtraces. No entanto, essa abordagem se torna menos eficaz com SwiftUI devido à sua natureza declarativa. O modelo de dados do SwiftUI, 'AttributeGraph', gerencia dependências entre visualizações, otimizando as atualizações. Quando uma visualização SwiftUI é declarada, está conforme o protocolo 'View' e define sua aparência e comportamento pela propriedade 'body'. Essa propriedade 'body' retorna outro valor 'View', e o SwiftUI gerencia o estado da visualização e as atualizações usando atributos. Alterações nas variáveis de estado acionam transações, marcando os atributos como desatualizados. O SwiftUI atualiza a hierarquia de visualizações durante o próximo quadro, percorrendo a cadeia de dependências para atualizar as partes necessárias. Para entender por que uma visualização SwiftUI foi atualizada, use o novo gráfico de Causa & Efeito do Instrument. Esse gráfico visualiza as relações entre as atualizações, mostrando a cadeia de causas desde interações do usuário, como gestos, até mudanças no estado e atualizações do corpo da visualização. Examinando esse gráfico, você pode identificar ineficiências, como atualizações desnecessárias, e otimizar o código de acordo. No app Landmarks, a classe 'ModelData' contém uma 'favoritesCollection', que armazena os pontos de referência favoritos em uma matriz. Cada 'LandmarkListItemView' verificava se um ponto de referência era favorito acessando 'favoritesCollection', criando uma dependência entre cada visualização de item e a matriz completa. Isso gerou ineficiência, pois, ao adicionar um favorito, o corpo de cada visualização de item era executado. Para resolver esse problema, a abordagem foi repensada. 'Observable' foi criado para cada ponto de referência, armazenando seu status de favorito. Cada 'LandmarkListItemView' tem seu próprio modelo de dados, eliminando a dependência da matriz completa de favoritos. Com essa alteração, o sistema atualiza o corpo da visualização necessário quando alguém altera o status de um favorito. Essa otimização melhora o desempenho, como demonstrado pela redução no número de atualizações do corpo da visualização observadas no gráfico de Causa & Efeito. O gráfico mostra como as atualizações no ambiente, como mudanças no esquema de cores, podem afetar as visualizações. Mesmo que o corpo de uma visualização seja executado devido a uma atualização no ambiente, ainda há um custo associado à verificação dessas atualizações. Evitar armazenar valores que mudam com frequência no ambiente.

    • 35:01 - Próximas etapas
    • Para o novo instrumento SwiftUI no Instruments 26, recursos adicionais, vídeos e materiais relacionados à análise e melhoria de desempenho de apps estão na documentação do desenvolvedor.

Developer Footer

  • Vídeos
  • WWDC25
  • Otimize o desempenho do SwiftUI com o Instruments
  • 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