После переноса уровней, текстур высокого разрешения или офлайн-моделей iOS-проекта в On-Demand Resources размер установочного пакета обычно заметно уменьшается. Однако успешная сборка еще не означает, что ресурсы доступны. Чаще всего проблемы возникают не из-за ошибок компиляции, а из-за пропущенных тегов, случайного включения всех ресурсов в тестовую сборку или несоответствия пакетов ресурсов в релизном артефакте запросам из кода. Облачный Mac позволяет оформить такие проверки как отдельное задание: каждый раз архивировать проект из чистей рабочей области, сохранять манифесты и контрольные суммы, а затем выполнять реальный запрос ресурсов.
Сначала определите критерии приемки
Приемка ODR должна охватывать как минимум три уровня: конфигурацию проекта, экспортированные артефакты и запросы во время выполнения. Проверка только одного из них неизбежно оставляет слепые зоны.
На уровне проекта нужно убедиться, что для Target включены On-Demand Resources, а каждый тег в каталоге ресурсов имеет четко определенное назначение. На уровне артефактов проверяются .assetpack, файлы манифестов и хеши файлов. На уровне выполнения необходимо проверить первый запрос, освобождение ресурсов и повторный запрос.
Запуска приложения недостаточно, чтобы считать ODR проверенными. Даже полное отсутствие ресурсов уровня, к которому еще не обращались, не помешает открыть начальный экран.
Для каждого тега рекомендуется сохранять четыре параметра: ответственного, экран-триггер, ожидаемый тип ресурсов и признак обязательности при первой установке. Эти сведения следует хранить в репозитории в виде обычного текстового манифеста, а не только в интерфейсе Xcode.
Управляйте разбиением с помощью стабильных тегов
Не следует создавать отдельный тег для каждой папки. При слишком мелком разбиении образуется множество небольших пакетов и возрастает частота запросов. При слишком крупном пользователю приходится загружать целую группу не связанных между собой ресурсов ради одного экрана. Надежнее группировать ресурсы по пользовательским сценариям, например level-01, tutorial-audio и model-basic, и следить, чтобы имена запросов в коде полностью совпадали с тегами в каталоге ресурсов.
Добавьте обязательную проверку манифеста тегов
В репозитории можно хранить список разрешенных тегов:
level-01
level-02
tutorial-audio
model-basic
Скрипт сборки должен извлекать теги из файла проекта или сгенерированных данных о ресурсах, сортировать их и сравнивать с этим списком. Добавление нового тега должно сопровождаться изменением манифеста. При удалении тега необходимо также проверить вызовы NSBundleResourceRequest в коде. Так опечатки будут выявляться еще на этапе слияния, а не во время выполнения.
В именах тегов следует использовать только стабильные символы ASCII, избегая пробелов, смешения регистра и временных номеров версий. Обновления ресурсов должны обозначаться версией содержимого или контрольной суммой. Не создавайте постоянно такие неподдерживаемые имена, как level-final-v2-new.
Зафиксируйте условия архивирования и экспорта
Для одного и того же коммита необходимо использовать фиксированные workspace, scheme, конфигурацию Release и параметры экспорта. Задание на узле VMArm может предварительно очищать каталог вывода текущего запуска, но не следует без разбора удалять глобальные кеши: иначе будет трудно определить, вызваны ли изменения ресурсами или окружением.
set -euo pipefail
: "${WORKSPACE:?WORKSPACE is required}"
: "${SCHEME:?SCHEME is required}"
ROOT="$PWD"
ARCHIVE_PATH="$ROOT/out/Game.xcarchive"
EXPORT_DIR="$ROOT/out/export"
rm -rf "$ARCHIVE_PATH" "$EXPORT_DIR"
mkdir -p "$EXPORT_DIR"
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath "$ARCHIVE_PATH" \
clean archive
xcodebuild \
-exportArchive \
-archivePath "$ARCHIVE_PATH" \
-exportPath "$EXPORT_DIR" \
-exportOptionsPlist "$ROOT/ci/ExportOptions.plist"
Для локальной приемки параметр embedOnDemandResourcesAssetPacksInBundle можно установить в true, чтобы сначала проверить содержимое отдельных пакетов и логику запросов. В производственном процессе следует использовать конфигурацию экспорта, соответствующую фактическому способу распространения, а не локальные настройки встраивания. Артефакты этих двух процессов необходимо сохранять в разных каталогах, чтобы тестовая сборка не перезаписала пакет, подготовленный к выпуску.
Выполните трехуровневую проверку артефактов
На первом уровне проверяется наличие файлов. На втором — возможность разбора plist. На третьем для всех файлов ресурсов создаются стабильные контрольные суммы, позволяющие сравнивать две сборки одного коммита.
set -euo pipefail
ARCHIVE_PATH="$PWD/out/Game.xcarchive"
EXPORT_DIR="$PWD/out/export"
REPORT_DIR="$PWD/out/report"
mkdir -p "$REPORT_DIR"
find "$ARCHIVE_PATH" "$EXPORT_DIR" \
-name '*.assetpack' \
-print | LC_ALL=C sort > "$REPORT_DIR/assetpacks.txt"
find "$ARCHIVE_PATH" "$EXPORT_DIR" \
-name 'AssetPackManifest.plist' \
-exec plutil -lint {} \; > "$REPORT_DIR/plist-lint.txt"
find "$ARCHIVE_PATH" "$EXPORT_DIR" \
-type f \
\( -name '*.car' -o -name '*.plist' -o -name '*.assetpack' \) \
-exec shasum -a 256 {} \; |
LC_ALL=C sort > "$REPORT_DIR/checksums.txt"
test -s "$REPORT_DIR/assetpacks.txt"
Если .assetpack фактически является каталогом, последняя команда хеширования не вычислит контрольные суммы его содержимого напрямую. Поэтому в проверку нужно также включить обычные файлы внутри этого каталога. В отчете следует указывать коммит, версию Xcode, scheme и хеш файла конфигурации экспорта, но не следует записывать закрытые ключи подписи, токены или полные пути к учетным данным.
При сравнении двух отчетов сначала отделите изменение порядка файлов от изменения их содержимого. Единая сортировка устраняет различия первого типа. Если хеши одного и того же коммита по-прежнему постоянно меняются, проверьте, не записывает ли скрипт генерации временные метки, абсолютные пути к рабочей области или случайные идентификаторы.
Проверка во время выполнения и поиск неисправностей
Тестирование во время выполнения следует начинать из чистого состояния. После установки приемочной сборки запросите тег, не требуемый при первоначальной установке, и зарегистрируйте три события: начало запроса, получение разрешения на доступ и освобождение ресурсов. Затем завершите работу приложения, очистите тестовое окружение и повторите процедуру, чтобы убедиться, что успех не был связан со старым кешем.
Порядок диагностики распространенных симптомов
Если запрос немедленно завершается ошибкой, сначала сопоставьте тег в коде с тегом в каталоге ресурсов. Если запрос долго остается в ожидании, проверьте правильность экспорта пакета ресурсов и тестовую сеть. Если локальная встроенная сборка работает, а производственная — нет, в первую очередь сравните два файла ExportOptions и конфигурации хостинга ресурсов. Если отсутствует только часть ресурсов, проверьте, не распределены ли одноименные файлы между несколькими тегами и не остались ли эти ресурсы в основном пакете.
Не маскируйте ошибки бесконечными повторными попытками. Задайте для запроса явный тайм-аут. В журнале ошибок фиксируйте только тег, домен ошибки, код ошибки и этап — не выводите токены доступа или конфиденциальные пути. После завершения приемки сохраните отчет, хеш конфигурации экспорта и журнал ошибок, а временные пакеты и тестовые кеши удалите. Тогда при следующем отклонении после изменения ODR можно будет сразу сравнить артефакты, а не заново выяснять, что сделал Xcode.
Часто задаваемые вопросы
Нужно ли встраивать ODR-пакеты в тестовую сборку?
Для локальной проверки это полезно: значение embedOnDemandResourcesAssetPacksInBundle=true позволяет сначала проверить состав пакетов без влияния сети. Производственный экспорт проверяют отдельно с настройками реального способа доставки.
Достаточно ли сравнить размер IPA?
Нет. Следует проверить количество файлов assetpack, соответствие тегов, корректность AssetPackManifest.plist, контрольные суммы и два запроса ресурса: до и после очистки локального кэша.
Выберите облачный Mac, который будет постоянно доступен для следующей сборки Xcode
Проверьте чип, память, хранилище, расчётный период и регион узла, затем перейдите к настройке. Фактическая доступность отображается в консоли в реальном времени.