Saltar al contenido
Development

Mi pestaña Discover estaba vacía porque le hice la pregunta equivocada a Apple Music

Por Victor Da Luz
iosswiftmusickitdev-logdeep-cut-atlas

Esta app se renombró después a Deep Cut Atlas. Acá abajo se la llama “Discoverer” en todo el texto, porque así se llamaba el día en que pasó esto.

La pestaña Discover en mi app es la razón de ser de la app entera: tomar los artistas ya presentes en la biblioteca de Apple Music y mostrar sus lanzamientos que todavía no se tienen. Deep cuts. La pestaña llegó a mi propio teléfono mostrando “Ya está todo al día” con una lista vacía, y la consola de Xcode escupía errores cada vez que cargaba. Función central, muerta.

El síntoma me mintió

Un estado vacío de “todo al día” y una consola llena de errores parecen dos errores distintos. Mi primer instinto fue pensar en limitación de tasa: la pestaña recorría toda mi biblioteca, obtenía el catálogo de cada artista, y comparaba. Disparar tantas solicitudes contra MusicKit y este responde con resistencia. Esa historia explicaba los errores pero no el feed vacío, y me tenía yendo hacia una solución de throttling antes de entender nada.

Lo honesto era dejar de adivinar y leer lo que el dispositivo realmente hacía. MusicKit no devuelve nada en el simulador, así que este es un error exclusivo de dispositivo físico. Conecté un diagnóstico descartable detrás de una bandera de lanzamiento, compilé e instalé en mi teléfono vía devicectl, y capturé la consola.

El código viejo hacía esto:

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

Tomaba un id de artista de la biblioteca del usuario y le pedía ese id al catálogo. Esos son espacios de id distintos. En el dispositivo, la solicitud no devuelve vacío en silencio, lanza un error HTTP (parecía un 404) para prácticamente todos los artistas. Eso era la inundación de la consola.

¿Y el feed vacío? Un puñado de artistas resultó devolver vacío en vez de lanzar un error, así que la protección “¿fallaron todos los artistas?” nunca se activó. El código concluyó “nada falló, simplemente no hay nada nuevo” y mostró “todo al día”. Los dos síntomas eran el mismo error con dos disfraces distintos.

Apple deliberadamente no expone el id de catálogo de un elemento de biblioteca (está escondido en un playParameters.catalogID privado). El puente soportado es with(_:preferredSource:): conservar el objeto Artist de biblioteca y pedirle que cargue una relación desde el catálogo.

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

Lo que no podía saber sin el dispositivo: ¿preferredSource: .catalog devuelve la discografía completa del artista, o solo los álbumes que ya se poseen? Es una preferencia, no una garantía, y si solo devolviera lo ya poseído el feed habría quedado vacío de una forma nueva y más confusa. Así que hice que el diagnóstico imprimiera, por artista, total / poseído / la diferencia. El dispositivo respondió con claridad:

'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

Discografía completa. Cortes reales después de la comparación. Una salvedad que vale la pena conocer: with(.catalog) limita .albums a unos 25 sin paginación explícita, lo cual alcanza de sobra para un feed pero es algo para tener presente con artistas prolíficos.

El otro número que importó: 1.610

El diagnóstico también imprimió el conteo de artistas de mi biblioteca: 1.610. El diseño anterior obtenía cada uno de esos catálogos en cada carga. Incluso con el error del id arreglado, son ~1.600 solicitudes de red para abrir una pestaña. Así que arreglar el id era necesario pero no suficiente.

Rediseñé el feed para que fuera incremental. Cada actualización revisa un lote acotado (40 artistas), guarda en caché los cortes de cada artista con una marca de tiempo de última revisión, y el feed es la unión de todo lo cacheado. Las actualizaciones van rotando por la biblioteca en vez de recorrerla entera cada vez.

La parte que más me gusta es el orden. Una pestaña de “descubrimiento” no debería priorizar los artistas que ya están en rotación pesada, debería resurgir a los que quedaron olvidados. Así que la clasificación pone primero a los artistas ausentes de lo reproducido recientemente, y después a los que llevan más tiempo sin revisarse. Lo reproducido recientemente pasó de ser algo que se favorecía a algo que se penaliza.

Lecciones

  • Un estado vacío y un estado de error pueden ser el mismo error. La pantalla de “todo al día” no era un problema separado de los errores de consola, era consecuencia de ellos. Perseguirlos por separado habría desperdiciado horas.
  • Para frameworks exclusivos de dispositivo, pasar al dispositivo temprano. Casi diseñé una solución de throttling alrededor de una causa adivinada. Lo más útil que hice fue imprimir conteos reales de mi biblioteca real.
  • Las APIs “preferidas” necesitan que su semántica se confirme, no que se asuma. preferredSource: .catalog compila sin importar si devuelve la discografía completa o solo lo poseído. Solo el dispositivo dice cuál de las dos.
  • El tamaño de la biblioteca es un dato de diseño. 1.610 artistas convirtieron “traer todo” de perezoso en roto.

Lecturas relacionadas