Swift Concurrency Series 07 | Armadilhas comuns ao combinar SwiftUI com async/await
A verdadeira armadilha geralmente não está na sintaxe, mas no fato de o “ciclo de vida da página” e o “ciclo de vida da tarefa” estarem alinhados.
SwiftUI e async/await são elegantes quando vistos individualmente, mas quando combinados, o problema mais facilmente exposto é o ciclo de vida.
Mais precisamente, é o deslocamento entre os dois ciclos de vida:
- Quando uma página SwiftUI aparece, redesenha ou desaparece?
- Quando uma tarefa assíncrona inicia, pausa, termina e cancela?
Se essas duas coisas não estiverem alinhadas, mesmo que o código possa ser executado hoje, ele crescerá facilmente mais tarde:
- Repetir solicitação
- Página piscando
- Escreva resultados antigos
- O status de carregamento é confuso
- A tarefa ainda é atualizada após sair da página
Então, o que este artigo realmente quer falar é: as páginas assíncronas do SwiftUI são propensas ao caos e quais são as causas por trás desse caos.
1. O erro de julgamento mais comum: tratar View como um objeto estável
Este é o hábito mais fácil para muitos desenvolvedores com experiência em UIKit adotarem.
Todos terão como padrão:
- A página aparece uma vez
- Solicitação para enviar uma vez
- Atualize a página atual após a solicitação voltar
Mas View no SwiftUI é mais como uma descrição de estado, em vez de uma instância estável que pode ser mantida por um longo tempo.
Isso significa que se o padrão em sua mente for “a visão à minha frente sempre estará lá, então esta tarefa pertence naturalmente a ela”, será fácil causar problemas mais tarde.
O SwiftUI não exige que as tarefas sejam vinculadas a um objeto estável, apenas é fácil se enganar pensando que já fez isso.
2. A maior armadilha do onAppear é que ele é usado como uma entrada de inicialização única.
Muitos artigos dirão: onAppear pode ser executado várias vezes.
Esta afirmação é verdadeira, mas não é suficiente.
O perigo real é que muitas vezes é escrito como a entrada padrão de fato para “inicialização de página”:
.onAppear {
Task {
await loadData()
}
}
O problema não é que esse código esteja errado, mas que é fácil adicioná-lo mentalmente:
“Quando esta página aparecer, ela será executada apenas uma vez.”
Depois de pensar desta forma, o seguinte aparecerá um após o outro:
- Repetir solicitação
- Redefinir status repetidamente
- Enterrar pontos repetidamente
- Os dados foram apagados logo após serem exibidos e reiniciados.
Portanto, uma ideia mais estável é: **Deixe o próprio processo assíncrono ser idempotente, desduplicado ou substituível. **
3. A segunda armadilha: misturar o status da página e o status da tarefa
Os estados comuns em uma página SwiftUI incluem:
- Critérios de filtro atuais
- Conteúdo de dados atual
- Está carregando?
- mensagem de erro
- Tarefas atualmente em execução
Se esses estados não estiverem claramente estratificados, eles podem facilmente ser agrupados.
Os maus cheiros mais comuns são:
-items
-isLoading
-error
-isRefreshing
-keyword
-selectedTab
Cada valor individualmente faz sentido, mas é difícil dizer como eles se relacionam. Então a página entrará em um estado muito estranho:
- Parece que ele está em qualquer condição
- Mas nenhum dos estados realmente expressa “em que semântica a página está agora”
Nesse caso, assim que o resultado assíncrono retornar, qualquer status poderá ser alterado, e o problema só será exposto mais cedo ou mais tarde.
4. A terceira armadilha: os resultados antigos são gravados de volta na IU atual
Este é um dos problemas mais frequentes nas páginas assíncronas do SwiftUI.
Os cenários típicos incluem:
- Os usuários podem alternar rapidamente entre guias
- As palavras-chave de pesquisa mudam continuamente
- Alterne repetidamente as condições do filtro
- A página aciona a atualização e o primeiro carregamento, um após o outro
Superficialmente, você pode pensar que apenas “enviou várias tarefas”, mas na verdade, o verdadeiro problema é:
**Embora a tarefa antiga ainda esteja legalmente concluída, ela não corresponde mais ao status atual da página. **
Uma vez que os resultados antigos ainda podem ser gravados na UI atual, a aparência que você vê geralmente é apenas:
- A página pisca
- A lista regride repentinamente
- O estado de carregamento termina repentinamente
- Mensagem de erro aparece inexplicavelmente
Esses fenômenos são muito semelhantes aos “pequenos bugs”, mas as causas raízes são muito semelhantes: A validade dos resultados da tarefa não é gerenciada.
5. O quarto poço: espalhe todas as entradas assíncronas na Visualização
Se essas entradas aparecerem em uma página ao mesmo tempo:
-onAppear { Task { ... } }
-refreshable { await ... }
-onChange(of:) { Task { ... } }
- Clique no botão para abrir outro
Task
Todos eles parecem legítimos individualmente, mas em conjunto rapidamente se tornam um problema:
**O relacionamento da tarefa está fora de controle. **
Tende a ficar cada vez mais difícil responder:
- quem é a entrada principal
- Quem deve cancelar quem?
- Quem está qualificado para alterar o status de exibição atual?
- A que ronda de tarefas corresponde uma determinada atualização de estado?
Portanto, muitas páginas assíncronas do SwiftUI têm muitas entradas, cada entrada pode acionar tarefas diretamente e, finalmente, não há uma camada de coordenação unificada.
6. A quinta armadilha: o padrão é “desde que a IU possa ser atualizada”
O SwiftUI esconde muitos detalhes de atualização da IU, o que dá às pessoas a ilusão de que, contanto que eu finalmente altere o status, a página será atualizada naturalmente.
Mas a verdadeira questão não é “será atualizado?” mas “se está qualificado para atualizar neste momento”.
Por exemplo:
- O resultado atual expirou?
- A página atual ainda está ativa?
- O status atual ainda corresponde a esta rodada de tarefas?
- A modificação atual deve ser feita na semântica do ator principal?
Se essas coisas não forem levadas a sério, a página pode não travar imediatamente, mas aos poucos acumulará muita “confusão acidental”.
7. Uma abordagem mais estável: deixe o View acionar a intenção e deixe a camada de estado gerenciar a tarefa
O método de organização que prefiro é:
- View é responsável por expressar a intenção do usuário
- ViewModel ou camada de estado é responsável por gerenciar tarefas e qualificações de resultados
- A visualização consome apenas o estado classificado
Dito isto, é melhor que o View saiba menos sobre estes detalhes:
- Se deseja cancelar tarefas antigas
- Qual resultado expirou
- O carregamento atual está sendo carregado pela primeira vez ou está sendo atualizado?
- Se o estado de erro deve substituir o conteúdo antigo
Se esses problemas permanecerem na Visualização, a página mudará rapidamente de “UI declarativa” para “shell declarativo envolto em uma camada de lógica assíncrona descentralizada”.
8. Uma sugestão mais próxima do combate real
Se uma página SwiftUI começa a ficar complicada, geralmente me forço a responder primeiro às seguintes perguntas:
- Quais entradas de tarefas existem na página?
- Se tarefas semelhantes coexistem, substituem ou ignoram.
- Quais estados são estados semânticos de página e quais são apenas estados de processos internos.
- Se os resultados antigos ainda podem ser alterados para a página atual.
- Após sair da página, quais tarefas devem continuar e quais devem ser interrompidas.
Se você não conseguir responder a essas perguntas, geralmente significa que o modelo de tarefa da página ainda não foi estabelecido.
9. Conclusão: A verdadeira dificuldade das páginas assíncronas do SwiftUI é o alinhamento do ciclo de vida
Uma situação comum é que as principais armadilhas entre SwiftUI e async/await são a sintaxe.
Mas a situação mais real é:
Viewnão é um objeto estável como você poderia pensar superficialmente.onAppearnão é uma semântica de inicialização única- Os resultados antigos não expirarão automaticamente porque “expiraram”
- Se houver muitas entradas de tarefas no nível da página, com certeza começará a ficar confuso.
Portanto, a página assíncrona SwiftUI verdadeiramente estável é:
Primeiro alinhe o ciclo de vida da página com o ciclo de vida da tarefa e, em seguida, escreva um código assíncrono específico.
Somente se isso for estabelecido com antecedência, a leveza do SwiftUI e a elegância do async/await se tornarão realmente vantagens, em vez de tornar o caos mais fácil de escrever.
What to read next
Want more posts about Swift Concurrency?
Posts in the same category are usually the best next step for reading more on this topic.
View same categoryWant to keep following #SwiftUI?
Tags are useful for related tools, specific problems, and similar troubleshooting notes.
View same tagWant to explore another direction?
If you are not sure what to read next, return to the homepage and start from categories, topics, or latest updates.
Back home