7. Usando Python no iOS¶
- Autores:
Russell Keith-Magee (2024-03)
Python no iOS é diferente do Python em plataformas de desktop. Em uma plataforma desktop, o Python geralmente é instalado como um recurso do sistema que pode ser usado por qualquer usuário daquele computador. Os usuários então interagem com o Python executando um executável python e inserindo comandos em um prompt interativo ou executando um script Python.
No iOS, não existe o conceito de instalação como recurso do sistema. A única unidade de distribuição de software é uma aplicação, ou “app”. Também não há console onde você possa executar um executável python ou interagir com um REPL do Python.
Como resultado, a única maneira de usar Python no iOS é no modo incorporado – ou seja, escrevendo uma aplicação iOS nativo e incorporando um interpretador Python usando libPython
e invocando o código Python usando API de incorporação do Python. O interpretador Python completo, a biblioteca padrão e todo o seu código Python são então empacotados como um pacote independente que pode ser distribuído pela iOS App Store.
Se você deseja experimentar pela primeira vez escrever uma aplicação iOS em Python, projetos como BeeWare e Kivy irão fornecer uma experiência de usuário muito mais acessível. Esses projetos gerenciam as complexidades associadas à execução de um projeto iOS, portanto, você só precisa lidar com o próprio código Python.
7.1. Python em tempo de execução no iOS¶
7.1.1. Compatibilidade com a versão do iOS¶
A versão mínima suportada do iOS é especificada em tempo de compilação, usando a opção --host
do configure
. Por padrão, quando compilado para iOS, Python será compilado com uma versão iOS mínima suportada de 13.0. Para usar uma versão mínima diferente do iOS, forneça o número da versão como parte do argumento --host
- por exemplo, --host=arm64-apple-ios15.4-simulator
compilaria um simulador ARM64 construído com uma meta de implantação de 15.4.
7.1.2. Identificação da plataforma¶
Ao executar no iOS, sys.platform
reportará como ios
. Este valor será retornado em um iPhone ou iPad, independentemente da aplicação estar executando no simulador ou em um dispositivo físico.
Informações sobre o ambiente de execução específico, incluindo a versão do iOS, modelo do dispositivo e se o dispositivo é um simulador, podem ser obtidas usando platform.ios_ver()
. platform.system()
reportará iOS
ou iPadOS
, dependendo do dispositivo.
os.uname()
reporta detalhes em nível de kernel; ele reportará o nome Darwin
.
7.1.3. Disponibilidade da biblioteca padrão¶
A biblioteca padrão do Python tem algumas omissões e restrições notáveis no iOS. Consulte o guia de disponibilidade de API para iOS para obter detalhes.
7.1.4. Módulos de extensão binária¶
Uma diferença notável sobre o iOS como plataforma é que a distribuição da App Store impõe requisitos rígidos ao empacotamento de uma aplicação. Um desses requisitos rege como os módulos de extensão binária são distribuídos.
A iOS App Store exige que todos os módulos binários em uma aplicação iOS sejam bibliotecas dinâmicas, contidas em um framework com metadados apropriados, armazenados na pasta Frameworks
da aplicação empacotada. Pode haver apenas um único binário por framework, e não pode haver nenhum material binário executável fora da pasta Frameworks
.
Isto entra em conflito com a abordagem usual do Python para distribuição de binários, que permite que um módulo de extensão binária seja carregado de qualquer local em sys.path
. Para garantir a conformidade com as políticas da App Store, um projeto iOS deve pós-processar quaisquer pacotes Python, convertendo módulos binários .so
em estruturas independentes individuais com metadados e assinatura apropriados. Para obter detalhes sobre como realizar esse pós-processamento, consulte o guia para adicionar Python ao seu projeto.
Para ajudar o Python a descobrir binários em seu novo local, o arquivo .so
original em sys.path
é substituído por um arquivo .fwork
. Este arquivo é um arquivo texto que contém a localização do binário do framework, relativo ao pacote de aplicações. Para permitir que o framework retorne ao local original, o framework deve conter um arquivo .origin
que contém a localização do arquivo .fwork
, relativo ao pacote da aplicação.
Por exemplo, considere o caso de uma importação from foo.bar import _whiz
, onde _whiz
é implementado com o módulo binário sources/foo/bar/_whiz.abi3.so
, com sources
sendo o local registrado em sys.path
, relativo ao pacote da aplicação. Este módulo deve ser distribuído como Frameworks/foo.bar._whiz.framework/foo.bar._whiz
(criando o nome do framework a partir do caminho de importação completo do módulo), com um arquivo Info.plist
no diretório .framework
identificando o binário como um framework. O módulo foo.bar._whiz
seria representado no local original com um arquivo marcador sources/foo/bar/_whiz.abi3.fwork
, contendo o caminho Frameworks/foo.bar._whiz/foo.bar._whiz
. O framework também conteria Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin
, contendo o caminho para o arquivo .fwork
.
Ao executar no iOS, o interpretador Python instalará um AppleFrameworkLoader
que é capaz de ler e importar arquivos .fwork
. Uma vez importado, o atributo __file__
do módulo binário reportará como a localização do arquivo .fwork
. Entretanto, o ModuleSpec
para o módulo carregado reportará a origin
como a localização do binário na pasta do framework.
7.1.5. Binários stub do compilador¶
O Xcode não expõe compiladores explícitos para iOS; em vez disso, ele usa um script xcrun
que resolve um caminho do compilador (por exemplo, xcrun --sdk iphoneos clang
para obter o clang
para um dispositivo iPhone). No entanto, usar este script apresenta dois problemas:
A saída de
xcrun
inclui caminhos que são específicos da máquina, resultando em um módulo sysconfig que não pode ser compartilhado entre usuários; eIsso resulta em definições
CC
/CPP
/LD
/AR
que incluem espaços. Existem muitas ferramentas do ecossistema C que pressupõem que você pode dividir uma linha de comando no primeiro espaço para obter o caminho para o executável do compilador; este não é o caso ao usarxcrun
.
Para evitar esses problemas, o Python forneceu stubs para essas ferramentas. Esses stubs são wrappers de script de shell em torno das ferramentas subjacentes xcrun
, distribuídos em uma pasta bin
distribuída junto com a estrutura iOS compilada. Esses scripts são relocáveis e sempre serão resolvidos para os caminhos apropriados do sistema local. Ao incluir esses scripts na pasta bin que acompanha um framework, o conteúdo do módulo sysconfig
se torna útil para usuários finais compilarem seus próprios módulos. Ao compilar módulos Python de terceiros para iOS, você deve garantir que esses binários stub estejam no seu caminho.
7.2. Instalando Python no iOS¶
7.2.1. Ferramentas para construir aplicações de iOS¶
A construção para iOS requer o uso das ferramentas Xcode da Apple. É altamente recomendável que você use a versão estável mais recente do Xcode. Isso exigirá o uso da versão mais recente (ou segunda) do macOS lançada recentemente, já que a Apple não mantém o Xcode para versões mais antigas do macOS. As ferramentas de linha de comando do Xcode não são suficientes para o desenvolvimento iOS; você precisa de uma instalação completa do Xcode.
Se quiser executar seu código no simulador iOS, você também precisará instalar um iOS Simulator Platform. Você deverá ser solicitado a selecionar um iOS Simulator Platform ao executar o Xcode pela primeira vez. Alternativamente, você pode adicionar um iOS Simulator Platform selecionando na guia Platforms do painel Settings do Xcode.
7.2.2. Adicionando Python a um projeto iOS¶
Python pode ser adicionado a qualquer projeto iOS, usando Swift ou Objective C. Os exemplos a seguir usarão Objective C; se você estiver usando Swift, poderá achar uma biblioteca como PythonKit útil.
Para adicionar Python a um projeto Xcode de iOS:
Build or obtain a Python
XCFramework
. See the instructions in Apple/iOS/README.md (in the CPython source distribution) for details on how to build a PythonXCFramework
. At a minimum, you will need a build that supportsarm64-apple-ios
, plus one of eitherarm64-apple-ios-simulator
orx86_64-apple-ios-simulator
.Arraste o
XCframework
para o seu projeto iOS. Nas instruções a seguir, presumiremos que você colocou oXCframework
na raiz do seu projeto; no entanto, você pode usar qualquer outro local desejado ajustando os caminhos conforme necessário.Adicione o código da sua aplicação como uma pasta no seu projeto Xcode. Nas instruções a seguir, presumiremos que seu código de usuário está em uma pasta chamada
app
na raiz do seu projeto; você pode usar qualquer outro local ajustando os caminhos conforme necessário. Certifique-se de que esta pasta esteja associada ao destino da sua aplicação.Selecione o destino da aplicação selecionando o nó raiz do seu projeto Xcode e, em seguida, o nome do destino na barra lateral que aparece.
Nas configurações de “General”, em “Frameworks, Libraries and Embedded Content”, adicione
Python.xcframework
, com “Embed & Sign” selecionado.Na guia “Build Settings”, modifique o seguinte:
Build Options
User Script Sandboxing: No
Enable Testability: Yes
Search Paths
Framework Search Paths:
$(PROJECT_DIR)
Header Search Paths:
"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"
Apple Clang - Warnings - All languages
Quoted Include In Framework Header: No
Add a build step that processes the Python standard library, and your own Python binary dependencies. In the “Build Phases” tab, add a new “Run Script” build step before the “Embed Frameworks” step, but after the “Copy Bundle Resources” step. Name the step “Process Python libraries”, disable the “Based on dependency analysis” checkbox, and set the script content to:
set -e source $PROJECT_DIR/Python.xcframework/build/build_utils.sh install_python Python.xcframework app
If you have placed your XCframework somewhere other than the root of your project, modify the path to the first argument.
Adicione código Objective C para inicializar e usar um interpretador Python em modo embarcado. Você deve garantir que:
Modo UTF-8 (
PyPreConfig.utf8_mode
) esteja habilitado;Stdio buffered (
PyConfig.buffered_stdio
) esteja desabilitado;Escrita de bytecode (
PyConfig.write_bytecode
) esteja desabilitada;Manipuladores de sinais (
PyConfig.install_signal_handlers
) estejam habilitados;Registro de logs do sistema (
PyConfig.use_system_logger
) esteja desabilitado (opcional, mas fortemente recomendado; isso está habilitado por padrão);PYTHONHOME
para o interpretador esteja configurado para apontar para a subpastapython
do pacote da sua aplicação; eO
PYTHONPATH
para o interpretador inclui:a subpasta
python/lib/python3.X
do pacote da sua aplicação,a subpasta
python/lib/python3.X/lib-dynload
do pacote da sua aplicação, ea subpasta
app
do pacote da sua aplicação
O local do pacote da sua aplicação pode ser determinada usando
[[NSBundle mainBundle] resourcePath]
.
Steps 7 and 8 of these instructions assume that you have a single folder of
pure Python application code, named app
. If you have third-party binary
modules in your app, some additional steps will be required:
You need to ensure that any folders containing third-party binaries are either associated with the app target, or are explicitly copied as part of step 7. Step 7 should also purge any binaries that are not appropriate for the platform a specific build is targeting (i.e., delete any device binaries if you’re building an app targeting the simulator).
If you’re using a separate folder for third-party packages, ensure that folder is added to the end of the call to
install_python
in step 7, and as part of thePYTHONPATH
configuration in step 8.Se alguma das pastas que contêm pacotes de terceiros contiver arquivos
.pth
, você deverá adicionar essa pasta como um diretório de site (usandosite.addsitedir()
), em vez de adicioná-la diretamente aPYTHONPATH
ousys.path
.
7.2.3. Testando um pacote Python¶
The CPython source tree contains a testbed project that is used to run the CPython test suite on the iOS simulator. This testbed can also be used as a testbed project for running your Python library’s test suite on iOS.
After building or obtaining an iOS XCFramework (see Apple/iOS/README.md
for details), create a clone of the Python iOS testbed project. If you used the
Apple
build script to build the XCframework, you can run:
$ python cross-build/iOS/testbed clone --app <path/to/module1> --app <path/to/module2> app-testbed
Or, if you’ve sourced your own XCframework, by running:
$ python Apple/testbed clone --platform iOS --framework <path/to/Python.xcframework> --app <path/to/module1> --app <path/to/module2> app-testbed
Any folders specified with the --app
flag will be copied into the cloned
testbed project. The resulting testbed will be created in the app-testbed
folder. In this example, the module1
and module2
would be importable
modules at runtime. If your project has additional dependencies, they can be
installed into the app-testbed/Testbed/app_packages
folder (using pip
install --target app-testbed/Testbed/app_packages
or similar).
Você pode então usar a pasta app-testbed
para executar o conjunto de testes para seu aplicativo. Por exemplo, se módulo1.tests
fosse o ponto de entrada para seu conjunto de testes, você poderia executar:
$ python app-testbed run -- módulo1.tests
Isso é o equivalente a executar python -m módulo1.tests
em uma compilação Python de desktop. Quaisquer argumentos após --
serão passados para o banco de testes como se fossem argumentos para python -m
em uma máquina de desktop.
Você também pode abrir o projeto de teste no Xcode executando:
$ open app-testbed/iOSTestbed.xcodeproj
Isso permitirá que você use o conjunto completo de ferramentas do Xcode para depuração.
Os argumentos usados para executar o conjunto de testes são definidos como parte do plano de teste. Para modificar o plano de teste, selecione o nó do plano de teste na árvore do projeto (deve ser o primeiro filho do nó raiz) e selecione a aba “Configurations”. Modifique o valor “Arguments Passed On Launch” para alterar os argumentos de teste.
The test plan also disables parallel testing, and specifies the use of the
Testbed.lldbinit
file for providing configuration of the debugger. The
default debugger configuration disables automatic breakpoints on the
SIGINT
, SIGUSR1
, SIGUSR2
, and SIGXFSZ
signals.
7.3. Conformidade com a App Store¶
O único mecanismo para distribuir aplicações para dispositivos iOS de terceiros é enviar a aplicação para a App Store do iOS; as aplicações enviadas para distribuição devem passar pelo processo de revisão de aplicações da Apple. Esse processo inclui um conjunto de regras de validação automatizadas que inspecionam o pacote de aplicação enviado em busca de código problemático.
A biblioteca padrão do Python contém alguns códigos que violam essas regras automatizadas. Embora essas violações pareçam ser falsos positivos, as regras de revisão da Apple não podem ser contestadas; portanto, é necessário modificar a biblioteca padrão do Python para que uma aplicação passe na revisão da App Store.
A árvore de fontes do Python contém um arquivo de patch que removerá todo o código que é conhecido por causar problemas no processo de revisão da App Store. Este patch é aplicado automaticamente quando o ao construir para o iOS.