6. Использование Python на iOS¶
- Авторы:
Рассел Кит-Мейджи (2024-03)
Python на iOS отличается от Python на настольных платформах. На настольных платформах Python обычно устанавливается как системный ресурс, который может использовать любой пользователь компьютера. Пользователи взаимодействуют с Python, запуская исполняемый файл python и вводя команды в интерактивной подсказке, или запуская сценарий Python.
В iOS нет понятия установки как системного ресурса. Единственной единицей распространения программного обеспечения является «приложение». Также нет консоли, где можно было бы запустить исполняемый файл python или взаимодействовать с Python REPL.
В результате единственный способ использовать Python на iOS - это встроенный режим, то есть написать родное приложение для iOS, встроить в него интерпретатор Python, используя libPython
, и вызывать код Python, используя Python embedding API. Полный интерпретатор Python, стандартная библиотека и весь ваш Python-код затем упаковываются в отдельный пакет, который можно распространять через iOS App Store.
Если вы хотите впервые попробовать написать iOS-приложение на Python, такие проекты, как BeeWare и Kivy, обеспечат вам гораздо более доступный пользовательский опыт. Эти проекты справляются со всеми сложностями, связанными с запуском iOS-проекта, так что вам нужно будет разобраться только с самим кодом на Python.
6.1. Python во время выполнения на iOS¶
6.1.1. Идентификация платформы¶
При выполнении на iOS значение sys.platform
будет отображаться как ios
. Это значение будет возвращено на iPhone или iPad, независимо от того, запущено ли приложение на симуляторе или на физическом устройстве.
Информацию о конкретной среде выполнения, включая версию iOS, модель устройства и наличие симулятора, можно получить с помощью platform.ios_ver()
. platform.system()
сообщит iOS
или iPadOS
, в зависимости от устройства.
os.uname()
сообщает подробности на уровне ядра; он сообщит имя Darwin
.
6.1.2. Наличие стандартной библиотеки¶
Стандартная библиотека Python имеет некоторые заметные упущения и ограничения для iOS. Подробности см. в разделе API availability guide for iOS.
6.1.3. Двоичные модули расширения¶
Одно из заметных отличий iOS как платформы заключается в том, что распространение App Store накладывает жесткие требования на упаковку приложения. Одно из этих требований определяет, как распространяются бинарные модули расширения.
iOS App Store требует, чтобы все двоичные модули в приложении для iOS были динамическими библиотеками, содержащимися во фреймворке с соответствующими метаданными и хранящимися в папке Frameworks
упакованного приложения. На каждый фреймворк может приходиться только один двоичный файл, а за пределами папки Frameworks
не может быть исполняемого двоичного материала.
Это противоречит обычному подходу Python к распространению двоичных файлов, который позволяет загружать двоичный модуль расширения из любого места на sys.path
. Чтобы обеспечить соответствие политикам App Store, проект iOS должен постобработать все пакеты Python, преобразовав бинарные модули .so
в отдельные автономные фреймворки с соответствующими метаданными и подписью. Подробнее о том, как выполнить эту постобработку, см. в руководстве для adding Python to your project.
Чтобы помочь Python обнаружить двоичные файлы в новом месте, оригинальный файл .so
на sys.path
заменяется файлом .fwork
. Этот файл представляет собой текстовый файл, содержащий расположение двоичного файла фреймворка относительно пакета приложений. Чтобы фреймворк мог вернуться в исходное расположение, он должен содержать файл .origin
, содержащий расположение файла .fwork
относительно пакета приложений.
Например, рассмотрим случай импорта from foo.bar import _whiz
, где _whiz
реализован с помощью бинарного модуля sources/foo/bar/_whiz.abi3.so
, причем sources
- это место, зарегистрированное в sys.path
, относительно пакета приложения. Этот модуль должен распространяться как Frameworks/foo.bar._whiz.framework/foo.bar._whiz
(создавая имя фреймворка из полного пути импорта модуля), с файлом Info.plist
в каталоге .framework
, идентифицирующим бинарник как фреймворк. Модуль foo.bar._whiz
будет представлен в исходном расположении с помощью файла-маркера sources/foo/bar/_whiz.abi3.fwork
, содержащего путь Frameworks/foo.bar._whiz/foo.bar._whiz
. Фреймворк также будет содержать Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin
, содержащий путь к файлу .fwork
.
При работе на iOS интерпретатор Python установит AppleFrameworkLoader
, способный читать и импортировать .fwork
файлы. После импорта атрибут __file__
бинарного модуля будет сообщать о местоположении .fwork
файла. Однако атрибут ModuleSpec
загруженного модуля будет указывать на origin
как на местоположение бинарного файла в папке framework.
6.1.4. Исполняемые файлы-заглушки компилятора¶
Xcode не предоставляет явных компиляторов для iOS; вместо этого он использует скрипт xcrun
, который разрешает полный путь к компилятору (например, xcrun --sdk iphoneos clang
для получения clang
для устройства iPhone). Однако использование этого скрипта создает две проблемы:
Вывод
xcrun
включает пути, специфичные для конкретной машины, что приводит к созданию модуля sysconfig, который нельзя использовать совместно с другими пользователями; иЭто приводит к тому, что определения
CC
/CPP
/LD
/AR
включают пробелы. Многие инструменты экосистемы Си предполагают, что вы можете разделить командную строку на первый пробел, чтобы получить путь к исполняемому файлу компилятора; это не так, если использоватьxcrun
.
Чтобы избежать этих проблем, Python предоставил заглушки для этих инструментов. Эти заглушки представляют собой обертки сценариев оболочки вокруг инструментов xcrun
, распространяемых в папке bin
, находящейся вместе со скомпилированным фреймворком iOS. Эти скрипты можно перемещать, и они всегда будут преобразовываться в соответствующие локальные системные пути. Благодаря включению этих скриптов в папку bin, сопровождающую фреймворк, содержимое модуля sysconfig
становится полезным для конечных пользователей при компиляции их собственных модулей. При компиляции сторонних модулей Python для iOS необходимо убедиться, что эти двоичные файлы-заглушки находятся на вашем пути.
6.2. Установка Python на iOS¶
6.2.1. Инструменты для создания приложений для iOS¶
Сборка под iOS требует использования инструментария Apple Xcode. Настоятельно рекомендуется использовать самый последний стабильный выпуск Xcode. Это потребует использования самой (или второй по счету) последней версии macOS, так как Apple не поддерживает Xcode для старых версий macOS. Инструментов командной строки Xcode недостаточно для разработки iOS; вам нужна полная установка Xcode.
Если вы хотите запустить свой код на симуляторе iOS, вам также потребуется установить iOS Simulator Platform. При первом запуске Xcode вам должно быть предложено выбрать платформу для симулятора iOS. Кроме того, вы можете добавить платформу iOS Simulator Platform, выбрав ее на вкладке Platforms панели настроек Xcode.
6.2.2. Добавление Python в проект iOS¶
Python можно добавить в любой iOS-проект, используя либо Swift, либо Objective C. В следующих примерах используется Objective C; если вы используете Swift, вам может пригодиться библиотека вроде PythonKit.
Чтобы добавить Python в проект iOS Xcode:
Соберите или получите Python
XCFramework
. Смотрите инструкции в iOS/README.rst (в исходном дистрибутиве CPython) для получения подробной информации о том, как собрать PythonXCFramework
. Как минимум, вам понадобится сборка, поддерживающаяarm64-apple-ios
, плюс один изarm64-apple-ios-simulator
илиx86_64-apple-ios-simulator
.Перетащите
XCframework
в свой проект iOS. В следующих инструкциях мы будем считать, что вы перетащилиXCframework
в корень вашего проекта; однако вы можете использовать любое другое место, корректируя пути по мере необходимости.Перетащите файл
iOS/Resources/dylib-Info-template.plist
в свой проект и убедитесь, что он связан с целевым приложением.Добавьте код приложения в качестве папки в проект Xcode. В следующих инструкциях мы предположим, что ваш пользовательский код находится в папке с именем
app
в корне вашего проекта; вы можете использовать любое другое место, изменяя пути по мере необходимости. Убедитесь, что эта папка связана с целью вашего приложения.Выберите цель приложения, выбрав корневой узел вашего проекта Xcode, а затем имя цели в появившейся боковой панели.
В настройках «Общие», в разделе «Фреймворки, библиотеки и встроенный контент», добавьте
Python.xcframework
, выбрав «Embed & Sign».На вкладке «Параметры сборки» измените следующие параметры:
Варианты сборки
Песочница пользовательских сценариев: Нет
Включить тестируемость: Да
Пути поиска
Пути поиска фреймворка:
$(PROJECT_DIR)
Пути поиска заголовков:
"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"
Apple Clang - Предупреждения - Все языки
Котировки включаются в заголовок фреймворка: Нет
Добавьте шаг сборки, который копирует стандартную библиотеку Python в ваше приложение. На вкладке «Build Phases» добавьте новый шаг сборки «Run Script» до шага «Embed Frameworks», но после шага «Copy Bundle Resources». Назовите шаг «Install Target Specific Python Standard Library», отключите флажок «Based on dependency analysis» и задайте содержание сценария:
set -e mkdir -p "$CODESIGNING_FOLDER_PATH/python/lib" if [ "$EFFECTIVE_PLATFORM_NAME" = "-iphonesimulator" ]; then echo "Installing Python modules for iOS Simulator" rsync -au --delete "$PROJECT_DIR/Python.xcframework/ios-arm64_x86_64-simulator/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" else echo "Installing Python modules for iOS Device" rsync -au --delete "$PROJECT_DIR/Python.xcframework/ios-arm64/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" fi
Обратите внимание, что название «кусочка» симулятора в XCframework может быть другим, в зависимости от архитектуры процессора, которую поддерживает ваш
XCFramework
.Добавьте второй шаг сборки, который обрабатывает бинарные модули расширения из стандартной библиотеки в формат «Framework». Добавьте шаг сборки «Run Script» прямо после шага, который вы добавили в шаге 8, под названием «Prepare Python Binary Modules». На нем также должен быть снят флажок «Based on dependency analysis», а содержание сценария должно быть следующим:
set -e install_dylib () { INSTALL_BASE=$1 FULL_EXT=$2 # The name of the extension file EXT=$(basename "$FULL_EXT") # The location of the extension file, relative to the bundle RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} # The path to the extension file, relative to the install base PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/} # The full dotted name of the extension module, constructed from the file path. FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d "." -f 1 | tr "/" "."); # A bundle identifier; not actually used, but required by Xcode framework packaging FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr "_" "-") # The name of the framework folder. FRAMEWORK_FOLDER="Frameworks/$FULL_MODULE_NAME.framework" # If the framework folder doesn't exist, create it. if [ ! -d "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ]; then echo "Creating framework for $RELATIVE_EXT" mkdir -p "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" cp "$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" plutil -replace CFBundleExecutable -string "$FULL_MODULE_NAME" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" plutil -replace CFBundleIdentifier -string "$FRAMEWORK_BUNDLE_ID" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" fi echo "Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME" mv "$FULL_EXT" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" # Create a placeholder .fwork file where the .so was echo "$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" > ${FULL_EXT%.so}.fwork # Create a back reference to the .so file location in the framework echo "${RELATIVE_EXT%.so}.fwork" > "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin" } PYTHON_VER=$(ls -1 "$CODESIGNING_FOLDER_PATH/python/lib") echo "Install Python $PYTHON_VER standard library extension modules..." find "$CODESIGNING_FOLDER_PATH/python/lib/$PYTHON_VER/lib-dynload" -name "*.so" | while read FULL_EXT; do install_dylib python/lib/$PYTHON_VER/lib-dynload/ "$FULL_EXT" done # Clean up dylib template rm -f "$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist" echo "Signing frameworks as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)..." find "$CODESIGNING_FOLDER_PATH/Frameworks" -name "*.framework" -exec /usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der "{}" \;
Добавьте код Objective C для инициализации и использования интерпретатора Python во встроенном режиме. Вы должны убедиться, что:
UTF-8 mode
является включенным;
Buffered stdio
является отключенным;
Writing bytecode
является отключенным;
Signal handlers
являются включенными;
PYTHONHOME
для интерпретатора настроен так, чтобы указывать на подпапкуpython
в папке вашего приложения; иВ состав
PYTHONPATH
для интерпретатора входят:
в подпапке
python/lib/python3.X
пакета вашего приложения,в подпапку
python/lib/python3.X/lib-dynload
пакета вашего приложения, ивложенная папка
app
в пакете вашего приложенияМестоположение связки вашего приложения можно определить с помощью
[[NSBundle mainBundle] resourcePath]
.
Шаги 8, 9 и 10 этой инструкции предполагают, что у вас есть одна папка с чистым кодом приложения на Python, названная app
. Если в вашем приложении есть бинарные модули сторонних разработчиков, потребуются дополнительные шаги:
Необходимо убедиться, что все папки, содержащие сторонние исполняемые файлы, либо связаны с целевым приложением, либо скопированы в них в рамках шага 8. На шаге 8 также следует очистить все двоичные файлы, которые не подходят для платформы, на которую нацелена конкретная сборка (например, удалите все двоичные файлы устройств, если вы собираете приложение для симулятора).
Все папки, содержащие двоичные файлы сторонних разработчиков, должны быть обработаны в виде фреймворка на шаге 9. Вызов
install_dylib
, который обрабатывает папкуlib-dynload
, можно скопировать и адаптировать для этой цели.Если вы используете отдельную папку для пакетов сторонних разработчиков, убедитесь, что эта папка включена в конфигурацию
PYTHONPATH
на шаге 10.