Pull to refresh
110.02
Surf
Создаём веб- и мобильные приложения

Правильная поддержка Apple Silicon: как избежать проблем при апгрейде с Intel (x86_64) на Apple (arm64)

Level of difficultyMedium
Reading time6 min
Views3.7K

Инженеры Apple придумали прекрасные по быстродействию и производительности процессоры Apple Silicon (M1, M1 Max и так далее) на архитектуре arm64. Но за полученное быстродействие разработчикам пришлось платить своим временем.  

Я — Никита Коробейников, iOS Team Lead в Surf. Расскажу, к каким проблемам мог привести апгрейд рабочего мака и что нужно учитывать с изобретением процессоров Apple Silicon.

Статья вдохновлена ограничениями в недавно вышедшем Xcode 14.3: запуск из-под Rosetta в нём стал deprecated.

Проблема

Однажды вы обновили свой мак и собирались приступить к выполнению рабочих задач. В вашем TODO-листе было несколько задачек на перекрашивание кнопок: типичный рабочий день iOS-разработчика. Вы были немного воодушевлены лишь тем, что на выходных к вам пришел новый Mac mini на M1. 

Вы ожидали что проект будет собираться быстрее, вы выполните поставленные задачи и сможете наконец отрефакторить легаси-код в освободившееся время, но что-то пошло не так… проект не собрался на симулятор.

Ошибка: сборка идёт на симулятор iOS, но в объектном файле к архитектуре arm64 привязана лишь платформа iOS
Ошибка: сборка идёт на симулятор iOS, но в объектном файле к архитектуре arm64 привязана лишь платформа iOS

Чем же вызвана эта ошибка? Дело в том, что маки с процессорами от Apple работают на архитектуре arm64, а старые маки с процессорами от Intel — на архитектуре x86_64.

Можно сказать, что архитектура определяет словарь машинного языка — совместимость с определенным набором команд. При компиляции написанный код переводится на машинный язык. Получается, что при сборке проекта на симулятор Xcode обнаружил несовместимость с набором команд архитектуры arm64 и симулятора iOS.

Временное решение

Решение было найдено быстро. На знаменитом Stack Overflow обладатели новеньких маков пришли на выручку друг другу.

"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;

Но со временем разработчики начали замечать проблемы.

Например:

  • необычно быстрый скролл при использовании SDK карт,

  • падения при прогоне UI-тестов.

Тем не менее это решение по-своему актуально: оно позволяет производить отладку приложения на симуляторе.

Откуда появляются проблемы

Исключая arm64 из списка архитектур, мы собираем пакет лишь под x86_64 — то есть архитектуру Intel-процессоров. Новые маки могут запускать программы x86_64 через динамический транслятор — Rosetta. Он обеспечивает совместимость со старыми десктопными приложениями. Без него маки с процессорами Apple Silicon на старте продаж имели бы в арсенале лишь системные приложения, поддерживаемые самой компанией Apple.

Проверить, как запустилась программа, можно через мониторинг системы — Activity Monitor. Если процесс запущен без транслятора Rosetta, в графе Kind будет Apple. Иначе — Intel. На старых маках эта графа попросту отсутствует.

Сейчас уже много сторонних приложений адаптированы под arm64 и запускаются нативно. Замечательно, что MacOS сам определяет, как должно быть запущено то или иное приложение. Однако эта самостоятельность системы приводит к тому, что становится неочевидно, как повлиять на запуск симулятора.

Чтобы симулятор на маках с процессорами Apple Silicon запускался без транслятора, разрабатываемые приложения и их зависимости должны быть адаптированы под архитектуру arm64.

Полноценное решение: XCFramework

Для решения проблем с упаковыванием сборок для разных таргетов под разные архитектуры был создан новый формат упаковки — XCFramework

Как работает XCFramework

XCFramework представляет упорядоченную структуру папок и спецификацию в Info.plist файле. Эта структура позволяет упаковать в одном файле сочетание платформ и архитектур, несовместимое в старых форматах.

Перейдём к примеру.

Пример xcframework, не адаптированного для симулятора на arm64. Обратите внимание на 'Item 0/SupportedArchitectures'
Пример xcframework, не адаптированного для симулятора на arm64. Обратите внимание на 'Item 0/SupportedArchitectures'

На картинке — пакет XCFramework библиотеки JRE (Java Runtime Environment). Она является частью официального релиза утилиты j2objc, позволяющей переводить Java-код на Objective-C. К слову, именно этот пакет вынуждал MacOS запускать проект через Rosetta. В спецификации видно, что пакет содержит библиотеку для симулятора лишь с x86_64 архитектурой (см. AvailableLibraries/Item 0/SupportedArchitectures).

На следующей картинке — та же зависимость JRE, но уже с полным необходимым набором архитектур для симулятора. Собрана вручную с того же официального релиза версии 2.8.

Пример xcframework, адаптированного для симулятора на arm64. Обратите внимание на 'Item 1/SupportedArchitectures'
Пример xcframework, адаптированного для симулятора на arm64. Обратите внимание на 'Item 1/SupportedArchitectures'

Сборка XCFramework из архивов

Перед упаковкой XCFramework требуется подготовить архивы с фреймворками.
Этот способ сборки возможен, если у вас есть доступ к исходникам проблемной зависимости.

Сборка архива с фреймворком:

xcodebuild archive \
           -project "${project}" \
           -scheme "${scheme}" \
           -sdk iphonesimulator \
           -archivePath "archives/${scheme}-sim" \
           ONLY_ACTIVE_ARCH=NO \
           SKIP_INSTALL=NO \
           BUILD_LIBRARY_FOR_DISTRIBUTION=YES

Флаг ONLY_ACTIVE_ARCH=NO позволяет включить в сборку все валидные архитектуры. Для каждой платформы существует свой набор валидных архитектур, который по умолчанию содержится в Xcode. Например, для симулятора это x86_64 и arm64. Для актуальных айфонов — arm64. Для часов и appleTV будут другие значения.

Флаг BUILD_LIBRARY_FOR_DISTRIBUTION=YES необходим для подготовки к дистрибьюции архива через XCFramewrok.

Создание  XCFramework из двух архивов фреймворка:

xcodebuild -create-xcframework \
       -framework "${archive_path}/${name}-ios.xcarchive/Products/Library/Frameworks/${name}.framework" \
       -framework "${archive_path}/${name}-sim.xcarchive/Products/Library/Frameworks/${name}.framework" \
       -output "${path}/${name}.xcframework"

Команда create-xcframework упакует несколько .framework в один XCFramework. 

Создание XCFramework из двух статических библиотек:

xcodebuild -create-xcframework \
   -library $result_dir/simulator-fat/${lib_name}.a \
   -library $result_dir/device/${lib_name}.a \
   -output $result_dir/${framework_name}.xcframework

Вместо .framework могут быть использованы статические библиотеки .a.
При необходимости можно также добавить публичные заголовки и dSym-файлы.

Мы показали, как собрать XCFramework, имея исходники. Но иногда проект может зависеть от статической библиотеки, доступ к исходникам которой отстутствует. Или библиотека устарела: это затрудняет её пересборку.

Проблема со статическими библиотеками

Статические библиотеки распространяются в скомпилированном виде, то есть уже переведены на машинный язык. Динамические переводятся в момент компиляции. Ошибка, которую мы обсуждаем, возникает именно при подключении в проект статической библиотеки — в файлах .a и .xcframework.

Как же распаковать то, что уже упаковано, и добавить привязку платформы iOS-симулятора к архитектуре arm64? Файлы с расширениями .a и .xcframework — это лишь способ представления и упаковки библиотек в Linux системах. Статическая библиотека может быть: 

  1. thin (худой) — содержит файлы, привязанные к одной архитектуре.

  2. fat (полной) — содержит несколько thin-файлов.

Для упрощения будем называть thin и fat-файлы в формате {платформа}_{архитектура/ы}.

Нам надо посмотреть информацию о внутренних библиотеках статического фреймворка: для этого есть lipo. Это мощный инструмент, позволяющий анализировать, создавать и извлекать содержимое статических библиотек.

Посмотреть информацию о внутренних библиотеках поможет команда lipo -info.

% lipo -info libjre_emul.a
Architectures in the fat file: libjre_emul.a are: arm64
% lipo -info libjre_emul.a	 
Architectures in the fat file: libjre_emul.a are: x86_64 arm64

Извлечь одну из thin-библиотек из fat-файла можно с помощью команды lipo -thin.

lipo -thin arm64 Example.framework/Example -output Example.arm64

Однако fat-библиотека не может содержать файлы, привязанные к одной и той же архитектуре. Иными словами, iphone_arm64 и ios_simulator_arm64 несовместимы в одном .a-файле. 

При этом они совместимы в XCFramework: просто нужно упаковать сборки для симулятора в одном fat-файле, а сборки для айфона — а в другом.

План готов, но чего-то не хватает. Откуда взять сборку ios_simulator_arm64 без исходников? Можно переопределить привязку библиотеки iphone_arm64, но нам придётся познакомиться с ещё одним инструментом. 

arm64_to_sim нас спасёт

Для начала надо извлечь все файлы из подопытной iphone_arm64. Используем штатный архиватор.

Разархивирование библиотеки ‘.a’:

ar x ${lib_name}.a

Затем с помощью arm64_to_sim патчим каждый .o файл (Compiled Object Format), включая вложенные файлы. Платформа iphone меняется на ios_simulator.

Привязка всех файлов под симулятор (синтаксис shell-zsh):

for file in ./*.o **/*.o ; do
   echo "Patching file: - ${file}";
   arm64-to-sim $file;
 done;

Собираем результат в библиотеку. Упаковка файлов в библиотеку ‘.a’:

ar crv ${archive_output_dir}/${archive_name}.a ${archive_input_dir}/**/*.o

И формируем fat-библиотеку для симулятора.

lipo -create -output $fat_output_dir/simulator-fat/${fat_lib_name}.a 
${fat_output_dir}/simulator-arm64/${fat_lib_name}.a 
${fat_output_dir}/simulator-64/${fat_lib_name}.a

Готово. Таким образом можно пропатчить любую статическую библиотеку, положить ее в XCFramework, как описано выше и… всё. Ваш проект будет окончательно адаптирован. Проблемы с отладкой на симуляторе уйдут, когда вы отключите временное решение с настройкой EXCLUDED_ARCH. 


С тех пор, как Apple начали выпускать маки на процессорах собственного производства, прошло уже много времени, поэтому многие разработчики библиотек уже пересобрали свои фреймворки в виде XCFramework с нужным набором архитектур. Однако по-прежнему можно наткнуться на неадаптированные библиотеки. 

По возможности избегайте использования флага EXCLUDED_ARCH для решения проблемы, описанной в статье, и попробуйте просто обновить зависимости. Если же обновление невозможно, вам может помочь переопределение привязки с arm64_to_sim.

Источники

Больше полезного про iOS — в нашем телеграм-канале Surf iOS Team. Публикуем кейсы, лучшие практики, новости и вакансии Surf. Присоединяйтесь >>

Tags:
Hubs:
Total votes 4: ↑4 and ↓0+4
Comments3

Articles

Information

Website
surf.ru
Registered
Founded
Employees
201–500 employees
Location
Россия