Pular para o conteúdo
Development

Minha aba Discover estava vazia porque fiz a pergunta errada para o Apple Music

Por Victor Da Luz
iosswiftmusickitdev-logdeep-cut-atlas

Esse app foi renomeado depois para Deep Cut Atlas. Ele é chamado de “Discoverer” ao longo do texto abaixo, porque era assim que se chamava no dia em que isso aconteceu.

A aba Discover do meu app é o motivo dele existir: pega os artistas que já estão na sua biblioteca do Apple Music e mostra os lançamentos deles que você ainda não tem. Deep cuts. A aba foi parar no meu próprio telefone mostrando “Você está em dia” com uma lista vazia, e o console do Xcode cuspia erros toda vez que carregava. Funcionalidade central, morta.

O sintoma mentiu para mim

Um estado vazio de “tudo em dia” e um console cheio de erros parecem dois bugs diferentes. Meu primeiro instinto foi limitação de taxa: a aba percorria toda a minha biblioteca, buscava o catálogo de cada artista, e comparava. Disparar tantas requisições para o MusicKit faz ele reagir. Essa história explicava os erros, mas não o feed vazio, e já estava me fazendo pensar em throttling antes de eu entender qualquer coisa.

A atitude honesta foi parar de adivinhar e ler o que o dispositivo realmente fez. O MusicKit não retorna nada no simulador, então esse é um bug só de dispositivo físico. Coloquei um diagnóstico descartável atrás de uma flag de inicialização, compilei e instalei no meu telefone via devicectl, e capturei o console.

O código antigo fazia isto:

var request = MusicCatalogResourceRequest<Artist>(matching: \.id, equalTo: MusicItemID(libraryArtist.id))
request.properties = [.albums]

Ele pegava o id de um artista da biblioteca do usuário e pedia esse id ao catálogo. Esses são espaços de id diferentes. No dispositivo, a requisição não retorna vazia silenciosamente; ela lança um erro HTTP (parecia um 404) para praticamente todo artista. É isso que inundava o console.

E o feed vazio? Alguns poucos artistas por acaso retornavam vazio em vez de lançar erro, então a checagem de “será que todo artista falhou?” nunca disparava. O código concluía “nada falhou, só não tem nada novo” e mostrava “tudo em dia.” Os dois sintomas eram o mesmo bug usando dois chapéus.

A Apple deliberadamente não expõe o id de catálogo de um item de biblioteca (ele fica escondido num playParameters.catalogID privado). A ponte suportada é with(_:preferredSource:): manter o objeto Artist de biblioteca e pedir para ele carregar um relacionamento vindo do catálogo.

let enriched = try await libraryArtist.with([.albums], preferredSource: .catalog)
let catalogAlbums = enriched.albums   // the artist's catalog discography

A coisa que eu não conseguia saber sem o dispositivo: será que preferredSource: .catalog devolve a discografia completa do artista, ou só os álbuns que eu já tenho? É uma preferência, não uma garantia, e um retorno só-do-que-eu-tenho significaria que o feed continuaria vazio de um jeito novo e mais confuso. Então fiz o diagnóstico imprimir, por artista, total / possuídos / a diferença. O dispositivo respondeu com clareza:

'A.N.I.M.A.L.'   total=25 owned=4 cuts=21
'A$AP Rocky'     total=25 owned=6 cuts=19
'Acid Bath'      total=7  owned=6 cuts=1

Discografia completa. Cuts reais depois da comparação. Uma ressalva que vale saber: with(.catalog) limita .albums a cerca de 25 sem paginação explícita, o que é suficiente para um feed, mas é algo para lembrar no caso de artistas prolíficos.

O outro número que importou: 1.610

O diagnóstico também imprimiu a contagem de artistas da minha biblioteca: 1.610. O design antigo buscava o catálogo de cada um deles a cada carregamento. Mesmo com o bug do id corrigido, isso são ~1.600 requisições de rede só para abrir uma aba. Então corrigir o id era necessário, mas não suficiente.

Refiz o feed para ser incremental. Cada atualização checa um lote limitado (40 artistas), guarda em cache os cuts de cada artista com um timestamp de última checagem, e o feed é a união de tudo que está em cache. As atualizações vão rodando pela biblioteca em vez de percorrê-la inteira de novo.

A parte de que mais gosto é a ordenação. Uma aba de “descoberta” não deveria priorizar os artistas que já estão em rotação pesada; ela deveria trazer de volta os que você esqueceu. Então a classificação coloca primeiro os artistas ausentes dos seus tocados recentemente, depois os que estão há mais tempo sem checagem. Tocados recentemente passou de algo que eu valorizaria a favor para algo que eu valorizo contra.

Lições

  • Um estado vazio e um estado de erro podem ser o mesmo bug. A tela de “tudo em dia” não era um problema separado dos erros no console; ela era consequência deles. Persegui-los separadamente teria desperdiçado horas.
  • Para frameworks só-de-dispositivo, vá para o dispositivo cedo. Quase desenhei uma correção de throttling em cima de uma causa adivinhada. A coisa mais útil que fiz foi imprimir contagens reais da minha biblioteca real.
  • APIs “preferidas” precisam ter a semântica confirmada, não presumida. preferredSource: .catalog compila tanto se retorna a discografia completa quanto se retorna só o que você possui. Só o dispositivo diz qual das duas.
  • O tamanho da biblioteca é um dado de design. 1.610 artistas transformaram “buscar tudo” de preguiçoso em quebrado.

Leitura relacionada

Development

A playlist que já tinha o nome certo

Um artefato de renomeação, uma API sem campo de autor pra atualizar, e um teste em dispositivo real provando que o bug já tinha se corrigido sozinho, fechado como aceitar como está.

Ler

Você também pode achar útil

Proton

Proton Drive

Armazenamento em nuvem criptografado, 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
AdGuard

AdGuard para iOS

Bloqueio de anúncios e rastreadores em todo o sistema no iOS, sem necessidade de um servidor DNS separado.

Como afiliado da AdGuard, ganho com compras qualificadas.

Saiba mais
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