On-Demand Resources queda obsoleto: límites y migración a Background Assets
Arturo Rivas Arias
Durante años, On-Demand Resources ha permitido publicar aplicaciones y juegos cuyo contenido no cabe —o no conviene incluir— dentro de la descarga inicial. En lugar de empaquetar todos los vídeos, audios, modelos 3D o mapas dentro de la aplicación, Xcode los agrupa en asset packs que el App Store aloja y entrega cuando son necesarios. El resultado es una instalación más pequeña y un primer arranque más rápido.
Sin embargo, esta tecnología ya ha entrado en su recta final. Apple ha marcado On-Demand Resources como obsoleto a partir de iOS 27, iPadOS 27, tvOS 27 y visionOS 27. Las aplicaciones existentes continuarán funcionando a corto plazo, pero el soporte se retirará en futuras versiones. Apple no ha anunciado todavía una fecha concreta para su eliminación, así que no hay una urgencia inmediata por retirar el código existente, aunque sí una señal inequívoca para no diseñar nuevas arquitecturas alrededor de NSBundleResourceRequest.
Antes de hablar de la migración conviene entender los límites actuales, porque siguen aplicándose mientras una aplicación utilice On-Demand Resources. Además, estos límites se calculan sobre cada variante después del app thinning, no necesariamente sobre el tamaño del archivo que vemos en el proyecto o en el archivo subido a App Store Connect. El App Store genera una variante adaptada a cada dispositivo y es el tamaño de esa variante el que cuenta.
Los límites en iOS y iPadOS
El salto más importante llegó con iOS 18 y iPadOS 18. Para esas versiones o posteriores, Apple duplicó el límite del bundle principal, multiplicó por dieciséis el tamaño máximo de cada asset pack y amplió considerablemente el almacenamiento total alojado.
| Concepto | Antes de iOS/iPadOS 18 | iOS/iPadOS 18 o posterior |
|---|---|---|
| Bundle descargado tras el thinning | 2 GB | 4 GB |
| Cada asset pack | 512 MB | 8 GB |
| Número de asset packs | 1.000 | 1.000 |
| Instalación inicial y etiquetas precargadas | 4 GB | Sin límite específico |
| Recursos en uso simultáneamente | 2 GB | Sin límite específico |
| Recursos alojados en el App Store | 20 GB | 70 GB |
“Sin límite” no significa que una aplicación pueda consumir almacenamiento infinito. Significa que App Store Connect ya no impone un máximo específico para esa categoría. La capacidad del dispositivo, el espacio libre, la política de almacenamiento del sistema y la experiencia del usuario siguen siendo restricciones reales. De hecho, el sistema puede purgar recursos descargados que ya no estén en uso.
Tampoco hay una relación exacta de uno a uno entre una etiqueta y un asset pack. Xcode construye los paquetes a partir de la combinación de etiquetas, recursos compartidos y variantes generadas durante el thinning, por lo que una sola etiqueta puede acabar representada en varios paquetes. El límite de 1.000 se aplica a los paquetes resultantes, no al número de nombres que hayamos definido en el proyecto.
El límite de recursos “en uso” depende además del ciclo de vida de NSBundleResourceRequest. Una etiqueta se considera en uso mientras exista al menos una petición que haya obtenido acceso y no lo haya finalizado. Olvidar endAccessingResources() no solo impide al sistema recuperar ese espacio: en aplicaciones destinadas a sistemas anteriores a iOS 18 también puede acercarnos innecesariamente al límite de 2 GB.
import Foundation
final class PronunciationResources {
private var request: NSBundleResourceRequest?
func prepareFrenchLessons(
completion: @escaping (Result<Void, Error>) -> Void
) {
let request = NSBundleResourceRequest(
tags: ["french-pronunciation-b2"]
)
self.request = request
request.beginAccessingResources { error in
if let error {
completion(.failure(error))
} else {
completion(.success(()))
}
}
}
func releaseLessons() {
request?.endAccessingResources()
request = nil
}
}
La API también permite asignar una prioridad de conservación a las etiquetas mediante Bundle.main.setPreservationPriority(_:forTags:). Es únicamente una indicación: cuando falta espacio, el sistema comienza por los recursos con menor prioridad. Asignar la prioridad máxima a todo elimina precisamente la información que el sistema necesita para decidir qué contenido puede purgar primero.
tvOS y visionOS no tienen exactamente los mismos límites
En tvOS el bundle principal puede ocupar hasta 4 GB tanto antes como después de tvOS 18. Cada asset pack continúa limitado a 512 MB, el contenido inicial y precargado mantiene un máximo de 4 GB y el total alojado sigue siendo de 20 GB. La única relajación relevante de tvOS 18 es la desaparición del límite específico de 2 GB para recursos en uso simultáneamente.
visionOS sigue un patrón más parecido al de iOS. A partir de visionOS 2, cada paquete puede alcanzar 8 GB, desaparecen los límites específicos para contenido inicial y recursos en uso, y el total alojado sube de 20 a 70 GB. El bundle principal mantiene un máximo de 4 GB. On-Demand Resources nunca ha estado disponible en macOS ni en watchOS, otra diferencia importante frente a su sustituto.
El verdadero problema no es el tamaño, sino el modelo de distribución
On-Demand Resources separa físicamente los recursos del bundle, pero su publicación sigue ligada a la versión de la aplicación. Cambiar un archivo obliga a generar, subir y poner en revisión un nuevo build, aunque el ejecutable no haya cambiado. Esa dependencia resulta especialmente costosa para catálogos multimedia, modelos de aprendizaje automático, contenido educativo o aplicaciones que actualizan recursos con frecuencia.
Managed Background Assets cambia ese modelo. Los recursos se organizan igualmente en asset packs, pero sus versiones se administran y distribuyen por separado de los builds de la aplicación. El sistema puede gestionar automáticamente las descargas, las actualizaciones y la descompresión, mientras que el equipo decide si aloja los archivos en su propia infraestructura o utiliza Apple-Hosted Background Assets.
Cada paquete adopta una de tres políticas de descarga:
essential: forma parte de la experiencia de instalación y está disponible antes de que la aplicación se abra por primera vez.prefetch: comienza a descargarse durante la instalación, aunque puede terminar después en segundo plano.onDemand: solo se descarga cuando la aplicación lo solicita explícitamente.
Una aplicación de aprendizaje de idiomas, por ejemplo, podría incluir su interfaz y las primeras lecciones en el bundle, marcar las voces del idioma seleccionado como essential, precargar ejercicios frecuentes con prefetch y dejar los cursos avanzados como onDemand. Así se evita descargar de entrada contenido para idiomas que una persona quizá nunca utilice.
{
"assetPackID": "FrenchPronunciationB2",
"downloadPolicy": {
"onDemand": {}
},
"fileSelectors": [
{
"directory": "Courses/French/B2/Audio"
}
],
"platforms": [
"iOS",
"macOS",
"visionOS"
]
}
El manifiesto se puede generar con xcrun ba-package template y convertir en un archivo distribuible mediante la misma herramienta ba-package. A diferencia de los recursos etiquetados dentro del catálogo de Xcode, el paquete tiene un identificador explícito, una política de descarga, una selección de archivos y una lista de plataformas compatibles.
Acceso moderno con concurrencia de Swift
La diferencia también se nota en el código. AssetPackManager es un actor y proporciona APIs asíncronas para localizar un paquete, garantizar que esté disponible y observar su progreso. El equivalente moderno del ejemplo anterior puede expresarse sin completion handlers y sin mantener vivo un objeto NSBundleResourceRequest para proteger los recursos.
import BackgroundAssets
import Foundation
actor PronunciationStore {
private let manager = AssetPackManager.shared
func audio(for lessonID: String) async throws -> Data {
let pack = try await manager.assetPack(
withID: "FrenchPronunciationB2"
)
try await manager.ensureLocalAvailability(of: pack)
return try manager.contents(
at: "Courses/French/B2/Audio/\(lessonID).m4a"
)
}
func removeDownloadedCourse() async throws {
try await manager.remove(
assetPackWithID: "FrenchPronunciationB2"
)
}
}
Si la descarga puede bloquear una acción visible, statusUpdates(forAssetPackWithID:) devuelve una secuencia asíncrona con la que actualizar el progreso de la interfaz. Cuando ensureLocalAvailability(of:) termina sin lanzar un error, el paquete ya está disponible localmente. Para archivos grandes, contents(at:) devuelve datos mapeados en memoria por defecto; también existe una API basada en descriptores cuando se necesita controlar la lectura a bajo nivel.
Managed Background Assets mantiene los paquetes actualizados, pero no elimina automáticamente el contenido descargado mientras la aplicación continúa instalada. La aplicación debe llamar a remove(assetPackWithID:) cuando sepa que un paquete ya no es necesario. El espacio ocupado aparece asociado a la aplicación en los ajustes de almacenamiento del dispositivo, así que la gestión explícita sigue siendo parte de una buena experiencia.
Apple-Hosted Background Assets amplía el techo hasta 200 GB
Apple incluye 200 GB de alojamiento por aplicación y permite hasta 200 asset packs. Esos límites se comparten entre todas las plataformas de la misma ficha de App Store Connect. No se trata de sumar indiscriminadamente todas las versiones del histórico: para cada identificador, Apple toma la versión elegible de mayor tamaño y después suma el máximo de cada paquete.
Si FrenchPronunciationB2 tiene una versión de 4 GB y otra de 2 GB, y ItalianPronunciationB2 tiene una versión de 1 GB, el consumo calculado es de 5 GB. Al alcanzar el 80 % de la capacidad, App Store Connect muestra un aviso y envía una notificación por correo. También es posible archivar paquetes para liberar espacio, aunque esa operación retira todas sus versiones de TestFlight y del App Store.
Los paquetes essential también influyen en el tamaño mostrado en la ficha del App Store. La cifra “Hasta” incluye todos los paquetes esenciales no localizados por idioma y, entre los localizados, el conjunto del idioma que más ocupa. Mover contenido fuera del bundle no garantiza por sí solo que el tamaño percibido de la instalación disminuya si ese mismo contenido sigue siendo esencial.
La compatibilidad entre versiones es el punto delicado
Un asset pack alojado por Apple no queda vinculado a un build concreto. La versión activa para distribución se entrega a todas las versiones instaladas de la aplicación dentro de ese contexto. Por tanto, publicar una nueva estructura de archivos puede romper un build antiguo que todavía espere las rutas anteriores.
La migración debe tratar el formato de los recursos como una API pública. Conviene mantener rutas compatibles, incluir una versión de esquema en los datos y hacer que cada build rechace de forma controlada los formatos que no comprenda. Cuando un cambio incompatible sea inevitable, puede publicarse bajo un identificador de paquete diferente y conservar temporalmente el anterior.
App Store Connect mantiene estados independientes para pruebas internas de TestFlight, pruebas externas y distribución en el App Store. Solo puede haber una versión activa de cada paquete en cada contexto. Esto permite validar una actualización antes de hacerla pública, pero exige probar también los builds antiguos que continuarán recibiéndola.
Una migración gradual
No es necesario reemplazar todos los recursos en una sola versión. Un plan razonable comienza inventariando etiquetas, tamaños y momentos de uso; después agrupa el contenido según las políticas essential, prefetch y onDemand; finalmente introduce paquetes administrados por áreas funcionales. Durante el periodo en que la aplicación soporte sistemas anteriores puede mantener una ruta compatible con On-Demand Resources o incluir una alternativa de descarga propia.
La nueva capacidad tampoco debería utilizarse como excusa para crear paquetes gigantes. Aunque un paquete pueda ocupar varios gigabytes, su tamaño afecta al tiempo de descarga, al espacio temporal necesario para procesarlo, a la recuperación frente a errores y a la cantidad de contenido que debe repetirse cuando cambia un único archivo. Separar los paquetes por ciclo de vida y frecuencia de actualización suele ser más importante que acercarse al máximo permitido.
On-Demand Resources seguirá siendo funcional durante un tiempo, pero ya no es una elección adecuada para proyectos nuevos. Sus límites actuales son amplios en iOS 18, iPadOS 18 y visionOS 2, aunque su dependencia del build y su futura retirada pesan más que esos 70 GB de capacidad. Managed Background Assets ofrece un modelo más flexible, APIs modernas basadas en concurrencia, soporte para más plataformas y hasta 200 GB alojados por Apple. La transición no consiste únicamente en cambiar una API: implica diseñar los recursos como contenido versionado, compatible y distribuible de manera independiente.