Pular para o conteúdo
Development

Os botões não estavam quebrados: a main thread estava ocupada

Por Victor Da Luz
iosswiftswiftuiperformancedev-logdeep-cut-atlas

Esse app foi renomeado depois para Deep Cut Atlas. Abaixo ele aparece como “Discoverer”, porque era esse o nome no dia em que isso aconteceu.

Redesenhei a aba History do meu app de Apple Music para abrir uma folha por faixa: tocar a música, adicionar o álbum a uma playlist, marcar como “sem interesse”, e ver mais do mesmo artista. Passou em 118 testes unitários. Passou numa rodada de automação de interface no simulador onde eu toquei em cada controle. Aí rodei no meu telefone e os botões da folha não responderam por um ou dois segundos depois que ela abriu.

Passei tempo demais chutando. Minha primeira teoria foi um bug de paginação. A segunda foi que mapear algumas centenas de modelos estava lento. As duas estavam erradas, e um revisor ficou me dizendo a mesma coisa: para de chutar, meça. Eu tinha pesquisado as ferramentas certas para isso, o instrumento Hangs do Instruments, o botão de Hang Detection no dispositivo em Ajustes → Developer, e depois tentei raciocinar meu caminho para não precisar delas.

A correção veio de uma frase de feedback real de dispositivo: “os botões de tocar, adicionar e ignorar não respondem até a seção ‘more’ carregar”.

Esse é o bug inteiro. A seção “more from this artist” carrega de forma assíncrona. Para mostrar só os álbuns que você ainda não tem, o serviço buscava sua biblioteca inteira do Apple Music e mapeava ela para chaves de comparação. O serviço é anotado com @MainActor, então esse mapeamento rodava na main thread. O SwiftUI processa toques de botão na main thread também. Enquanto o mapeamento da biblioteca rodava, cada toque só ficava na fila. Os botões estavam bem. A thread estava ocupada.

O .task do SwiftUI e as ações de botão de uma view rodam no main actor por padrão. Um await numa chamada de rede libera a thread, mas o trabalho síncrono entre awaits, como mapear milhares de álbuns, não libera. Com uma biblioteca pequena você nunca notaria. Com uma real é um travamento visível.

A correção foi não fazer esse trabalho nesse caminho. A tela History já tinha calculado as chaves da biblioteca quando carregou. Então passei elas para a folha e filtrei as sugestões no cliente, e disse para a busca do catálogo pular a passada pela biblioteca inteira. Os botões respondem instantaneamente agora porque a main thread não está fazendo nada pesado enquanto a folha está aberta.

A mesma forma explicava um segundo travamento, voltar para a lista relia a biblioteca inteira e rodava uma busca síncrona no banco de dados a cada fechamento. Só precisava reconferir a única playlist que talvez tivesse mudado, e só quando algo realmente mudou.

A lição que eu de fato guardei não foi técnica. Testes unitários e automação de simulador verificam lógica e renderização. Eles não verificam se um toque parece instantâneo, e rodam em dados mock que nunca reproduzem a performance de uma biblioteca real. Então adicionei uma regra ao projeto: nenhuma funcionalidade de interface está “pronta” até uma passada em dispositivo com Hang Detection ligado confirmar que todo toque responde rápido e que a ação de fato persistiu. As ferramentas para pegar isso existiram o tempo todo. Eu só precisava me obrigar a usar elas antes de chamar algo de terminado.

Leitura relacionada

Development

O scan de 8 segundos escondido em cada refresh

Medir no dispositivo encontrou um scan da biblioteca inteira rodando em cada lote do Discover - e uma armadilha de medição de 16x onde o cache HTTP favoreceu o caminho errado.

Ler

Você também pode achar útil

Airalo

eSIM Airalo

eSIM de dados local para viagens - sem necessidade de trocar um SIM físico.

Este é meu link de indicação da Airalo. Você recebe um desconto no seu primeiro eSIM e eu ganho crédito da Airalo para o meu.

Saiba mais
Proton

Proton Pass

Gerenciador de senhas focado em privacidade, da equipe por trás do Proton Mail.

Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).

Saiba mais
Proton

Proton VPN

VPN comercial com filtragem NetShield e interruptor de desligamento automático.

Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).

Saiba mais