7. Python auf iOS verwenden

Autoren:

Russell Keith-Magee (2024-03)

Python auf iOS unterscheidet sich von Python auf Desktop-Plattformen. Auf einer Desktop-Plattform wird Python in der Regel als Systemressource installiert, die von jedem Benutzer dieses Computers verwendet werden kann. Benutzer interagieren dann mit Python, indem sie eine python-ausführbare Datei ausführen und Befehle in einer interaktiven Eingabeaufforderung eingeben oder ein Python-Skript ausführen.

Auf iOS gibt es kein Konzept der Installation als Systemressource. Die einzige Einheit der Softwareverteilung ist eine "App". Es gibt auch keine Konsole, auf der Sie eine python-ausführbare Datei ausführen oder mit einer Python REPL interagieren könnten.

Daher ist die einzige Möglichkeit, Python auf iOS zu verwenden, der eingebettete Modus – das heißt, Sie schreiben eine native iOS-Anwendung und betten einen Python-Interpreter mithilfe von libPython ein und rufen Python-Code über die Python Embedding API auf. Der vollständige Python-Interpreter, die Standardbibliothek und Ihr gesamter Python-Code werden dann als eigenständiges Paket verpackt, das über den iOS App Store vertrieben werden kann.

Wenn Sie zum ersten Mal mit dem Schreiben einer iOS-App in Python experimentieren möchten, bieten Projekte wie BeeWare und Kivy eine wesentlich zugänglichere Benutzererfahrung. Diese Projekte verwalten die Komplexität, die mit dem Ausführen eines iOS-Projekts verbunden ist, sodass Sie sich nur mit dem Python-Code selbst befassen müssen.

7.1. Python zur Laufzeit auf iOS

7.1.1. iOS-Versionskompatibilität

Die minimal unterstützte iOS-Version wird zur Kompilierungszeit mithilfe der Option --host für configure angegeben. Standardmäßig wird Python beim Kompilieren für iOS mit einer minimal unterstützten iOS-Version von 13.0 kompiliert. Um eine andere minimale iOS-Version zu verwenden, geben Sie die Versionsnummer als Teil des Arguments --host an – zum Beispiel würde --host=arm64-apple-ios15.4-simulator einen ARM64-Simulator-Build mit einem Deployment-Ziel von 15.4 kompilieren.

7.1.2. Plattformidentifikation

Beim Ausführen auf iOS meldet sys.platform als ios. Dieser Wert wird auf einem iPhone oder iPad zurückgegeben, unabhängig davon, ob die App auf dem Simulator oder einem physischen Gerät ausgeführt wird.

Informationen über die spezifische Laufzeitumgebung, einschließlich der iOS-Version, des Gerätemodells und ob es sich bei dem Gerät um einen Simulator handelt, können mit platform.ios_ver() abgerufen werden. platform.system() gibt je nach Gerät iOS oder iPadOS zurück.

os.uname() gibt Details auf Kernel-Ebene zurück; es gibt einen Namen von Darwin zurück.

7.1.3. Verfügbarkeit der Standardbibliothek

Die Python-Standardbibliothek weist auf iOS einige bemerkenswerte Auslassungen und Einschränkungen auf. Details finden Sie im API-Verfügbarkeitsleitfaden für iOS.

7.1.4. Binäre Erweiterungsmodule

Ein bemerkenswerter Unterschied bei iOS als Plattform ist, dass die Verteilung über den App Store strenge Anforderungen an die Paketierung einer Anwendung stellt. Eine dieser Anforderungen betrifft die Verteilung von binären Erweiterungsmodulen.

Der iOS App Store verlangt, dass *alle* binären Module in einer iOS-App dynamische Bibliotheken sein müssen, die in einem Framework mit entsprechender Metadaten enthalten sind und im Ordner Frameworks der gepackten App gespeichert werden. Es kann nur ein Binärcode pro Framework geben, und es darf kein ausführbares Binärmaterial außerhalb des Ordners Frameworks vorhanden sein.

Dies steht im Widerspruch zum üblichen Python-Ansatz für die Verteilung von Binärdateien, der es erlaubt, ein binäres Erweiterungsmodul von jedem Ort auf sys.path zu laden. Um die Einhaltung der App Store-Richtlinien sicherzustellen, muss ein iOS-Projekt alle Python-Pakete nachbearbeiten und .so-Binärmodule in einzelne, eigenständige Frameworks mit entsprechender Metadaten und Signierung umwandeln. Details zur Durchführung dieser Nachbearbeitung finden Sie im Leitfaden zum Hinzufügen von Python zu Ihrem Projekt.

Um Python beim Auffinden von Binärdateien an ihrem neuen Speicherort zu helfen, wird die ursprüngliche .so-Datei auf sys.path durch eine .fwork-Datei ersetzt. Diese Datei ist eine Textdatei, die den Speicherort der Framework-Binärdatei relativ zum App-Bundle enthält. Damit das Framework auf den ursprünglichen Speicherort zurückverweisen kann, muss das Framework eine .origin-Datei enthalten, die den Speicherort der .fwork-Datei relativ zum App-Bundle enthält.

Betrachten wir zum Beispiel den Import from foo.bar import _whiz, wobei _whiz mit dem binären Modul sources/foo/bar/_whiz.abi3.so implementiert ist, wobei sources der auf sys.path registrierte Speicherort ist, relativ zum Anwendungsbundle. Dieses Modul *muss* als Frameworks/foo.bar._whiz.framework/foo.bar._whiz verteilt werden (der Framework-Name wird aus dem vollständigen Importpfad des Moduls erstellt), mit einer Info.plist-Datei im Verzeichnis .framework, die die Binärdatei als Framework identifiziert. Das Modul foo.bar._whiz würde am ursprünglichen Speicherort mit einer Marker-Datei sources/foo/bar/_whiz.abi3.fwork repräsentiert, die den Pfad Frameworks/foo.bar._whiz/foo.bar._whiz enthält. Das Framework würde auch Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin enthalten, das den Pfad zur .fwork-Datei enthält.

Beim Ausführen auf iOS installiert der Python-Interpreter einen AppleFrameworkLoader, der .fwork-Dateien lesen und importieren kann. Nach dem Import meldet das Attribut __file__ des binären Moduls den Speicherort der .fwork-Datei. Die ModuleSpec für das geladene Modul meldet jedoch origin als den Speicherort der Binärdatei im Framework-Ordner.

7.1.5. Compiler-Stub-Binaries

Xcode stellt keine expliziten Compiler für iOS zur Verfügung; stattdessen verwendet es ein Skript xcrun, das zu einem vollständigen Compilerpfad aufgelöst wird (z. B. xcrun --sdk iphoneos clang, um den clang für ein iPhone-Gerät zu erhalten). Die Verwendung dieses Skripts birgt jedoch zwei Probleme:

  • Die Ausgabe von xcrun enthält maschinenspezifische Pfade, was zu einem sysconfig-Modul führt, das nicht zwischen Benutzern geteilt werden kann; und

  • Dies führt zu CC/CPP/LD/AR-Definitionen, die Leerzeichen enthalten. Es gibt viele C-Ökosystem-Tools, die davon ausgehen, dass Sie eine Kommandozeile beim ersten Leerzeichen aufteilen können, um den Pfad zur ausführbaren Compilerdatei zu erhalten; das ist bei der Verwendung von xcrun nicht der Fall.

Um diese Probleme zu vermeiden, stellt Python Stubs für diese Werkzeuge bereit. Diese Stubs sind Shell-Skript-Wrapper um die zugrunde liegenden xcrun-Werkzeuge, die in einem Ordner bin verteilt werden, der neben dem kompilierten iOS-Framework liegt. Diese Skripte sind relokierbar und werden immer auf die entsprechenden lokalen Systempfade aufgelöst. Durch die Einbeziehung dieser Skripte in den Ordner bin, der ein Framework begleitet, wird der Inhalt des sysconfig-Moduls für Endbenutzer nützlich, um ihre eigenen Module zu kompilieren. Beim Kompilieren von Python-Modulen von Drittanbietern für iOS sollten Sie sicherstellen, dass diese Stub-Binärdateien in Ihrem Pfad enthalten sind.

7.2. Python auf iOS installieren

7.2.1. Werkzeuge zum Erstellen von iOS-Apps

Für die Erstellung unter iOS ist die Verwendung der Xcode-Werkzeuge von Apple erforderlich. Es wird dringend empfohlen, die neueste stabile Version von Xcode zu verwenden. Dies erfordert die Verwendung der neuesten (oder zweitneuesten) macOS-Version, da Apple Xcode nicht für ältere macOS-Versionen pflegt. Die Xcode Command Line Tools reichen für die iOS-Entwicklung nicht aus; Sie benötigen eine *vollständige* Xcode-Installation.

Wenn Sie Ihren Code auf dem iOS-Simulator ausführen möchten, müssen Sie auch eine iOS-Simulator-Plattform installieren. Sie werden aufgefordert, eine iOS-Simulator-Plattform auszuwählen, wenn Sie Xcode zum ersten Mal ausführen. Alternativ können Sie eine iOS-Simulator-Plattform über die Registerkarte "Plattformen" im Einstellungsfenster von Xcode hinzufügen.

7.2.2. Hinzufügen von Python zu einem iOS-Projekt

Python kann zu jeder iOS-Projekt mit Swift oder Objective-C hinzugefügt werden. Die folgenden Beispiele verwenden Objective-C. Wenn Sie Swift verwenden, ist eine Bibliothek wie PythonKit möglicherweise hilfreich.

So fügen Sie Python zu einem iOS Xcode-Projekt hinzu:

  1. Erstellen oder beschaffen Sie ein Python XCFramework. Anweisungen zum Erstellen eines Python XCFramework finden Sie in Apple/iOS/README.md (in der CPython-Quellcode-Distribution). Mindestens benötigen Sie eine Build, die arm64-apple-ios unterstützt, plus entweder arm64-apple-ios-simulator oder x86_64-apple-ios-simulator.

  2. Ziehen Sie das XCframework in Ihr iOS-Projekt. In den folgenden Anweisungen gehen wir davon aus, dass Sie das XCframework in das Stammverzeichnis Ihres Projekts gezogen haben. Sie können jedoch jeden anderen gewünschten Speicherort verwenden, indem Sie die Pfade entsprechend anpassen.

  3. Fügen Sie Ihren Anwendungscode als Ordner in Ihr Xcode-Projekt ein. In den folgenden Anweisungen gehen wir davon aus, dass Ihr Benutzercode in einem Ordner namens app im Stammverzeichnis Ihres Projekts liegt. Sie können jeden anderen Speicherort verwenden, indem Sie die Pfade entsprechend anpassen. Stellen Sie sicher, dass dieser Ordner mit Ihrem App-Ziel verknüpft ist.

  4. Wählen Sie das App-Ziel aus, indem Sie den Stammknoten Ihres Xcode-Projekts und dann den Zielnamen in der angezeigten Seitenleiste auswählen.

  5. Fügen Sie unter "General", "Frameworks, Libraries and Embedded Content" Python.xcframework hinzu, wobei "Embed & Sign" ausgewählt ist.

  6. Wechseln Sie zur Registerkarte "Build Settings" und ändern Sie Folgendes:

    • Build-Optionen

      • User Script Sandboxing: No

      • Enable Testability: Yes

    • Suchpfade

      • 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

  7. Fügen Sie einen Build-Schritt hinzu, der die Python-Standardbibliothek und Ihre eigenen Python-Binärabhängigkeiten verarbeitet. Fügen Sie unter "Build Phases" einen neuen "Run Script"-Build-Schritt *vor* dem Schritt "Embed Frameworks", aber *nach* dem Schritt "Copy Bundle Resources" hinzu. Benennen Sie den Schritt "Process Python libraries", deaktivieren Sie das Kontrollkästchen "Based on dependency analysis" und legen Sie den Skriptinhalt auf Folgendes fest:

    set -e
    source $PROJECT_DIR/Python.xcframework/build/build_utils.sh
    install_python Python.xcframework app
    

    Wenn Sie Ihr XCframework an einem anderen Ort als dem Stammverzeichnis Ihres Projekts platziert haben, passen Sie den Pfad zum ersten Argument an.

  8. Fügen Sie Objective-C-Code hinzu, um einen Python-Interpreter im eingebetteten Modus zu initialisieren und zu verwenden. Sie sollten sicherstellen, dass:

    • UTF-8-Modus (PyPreConfig.utf8_mode) *aktiviert* ist;

    • Puffered Standard-I/O (PyConfig.buffered_stdio) *deaktiviert* ist;

    • Schreiben von Bytecode (PyConfig.write_bytecode) *deaktiviert* ist;

    • Signalhandler (PyConfig.install_signal_handlers) *aktiviert* sind;

    • System-Logging (PyConfig.use_system_logger) *aktiviert* ist (optional, aber dringend empfohlen; dies ist standardmäßig aktiviert);

    • PYTHONHOME für den Interpreter so konfiguriert ist, dass er auf den Unterordner python des App-Bundles verweist; und

    • Der PYTHONPATH für den Interpreter Folgendes enthält:

      • den Unterordner python/lib/python3.X Ihres App-Bundles,

      • den Unterordner python/lib/python3.X/lib-dynload Ihres App-Bundles und

      • den Unterordner app Ihres App-Bundles

    Der Speicherort des App-Bundles kann mit [[NSBundle mainBundle] resourcePath] ermittelt werden.

Die Schritte 7 und 8 dieser Anweisungen gehen davon aus, dass Sie einen einzigen Ordner mit reinem Python-Anwendungscode namens app haben. Wenn Sie binäre Module von Drittanbietern in Ihrer App haben, sind einige zusätzliche Schritte erforderlich:

  • Sie müssen sicherstellen, dass alle Ordner, die Binärdateien von Drittanbietern enthalten, entweder mit dem App-Ziel verknüpft sind oder explizit als Teil von Schritt 7 kopiert werden. Schritt 7 sollte auch alle Binärdateien löschen, die für die Plattform, die ein bestimmter Build anvisiert, nicht geeignet sind (d. h. löschen Sie alle Geräte-Binärdateien, wenn Sie eine App für den Simulator erstellen).

  • Wenn Sie einen separaten Ordner für Drittanbieterpakete verwenden, stellen Sie sicher, dass dieser Ordner am Ende des Aufrufs von install_python in Schritt 7 hinzugefügt wird und als Teil der PYTHONPATH-Konfiguration in Schritt 8.

  • Wenn einer der Ordner, die Drittanbieterpakete enthalten, .pth-Dateien enthält, sollten Sie diesen Ordner als *Site-Verzeichnis* hinzufügen (mit site.addsitedir()) anstatt ihn direkt zu PYTHONPATH oder sys.path hinzuzufügen.

7.2.3. Testen eines Python-Pakets

Der CPython-Quellbaum enthält ein Testbett-Projekt, das zum Ausführen der CPython-Testsuite auf dem iOS-Simulator verwendet wird. Dieses Testbett kann auch als Testbett-Projekt zum Ausführen der Testsuite Ihrer Python-Bibliothek auf iOS verwendet werden.

Nachdem Sie ein iOS XCFramework erstellt oder beschafft haben (siehe Apple/iOS/README.md für Details), erstellen Sie einen Klon des Python iOS Testbett-Projekts. Wenn Sie das Apple-Build-Skript zum Erstellen des XCframeworks verwendet haben, können Sie Folgendes ausführen:

$ python cross-build/iOS/testbed clone --app <path/to/module1> --app <path/to/module2> app-testbed

Oder, wenn Sie Ihr eigenes XCframework bezogen haben, durch Ausführen von:

$ python Apple/testbed clone --platform iOS --framework <path/to/Python.xcframework> --app <path/to/module1> --app <path/to/module2> app-testbed

Alle mit dem Flag --app angegebenen Ordner werden in das geklonte Testbett-Projekt kopiert. Das resultierende Testbett wird im Ordner app-testbed erstellt. In diesem Beispiel wären module1 und module2 zur Laufzeit importierbare Module. Wenn Ihr Projekt zusätzliche Abhängigkeiten hat, können diese in den Ordner app-testbed/Testbed/app_packages installiert werden (mit pip install --target app-testbed/Testbed/app_packages oder ähnlichem).

Sie können dann den Ordner app-testbed verwenden, um die Testsuite für Ihre App auszuführen. Wenn beispielsweise module1.tests der Einstiegspunkt zu Ihrer Testsuite war, könnten Sie Folgendes ausführen:

$ python app-testbed run -- module1.tests

Dies entspricht der Ausführung von python -m module1.tests auf einem Desktop-Python-Build. Alle Argumente nach dem -- werden an das Testbett übergeben, als wären sie Argumente für python -m auf einem Desktop-Computer.

Sie können das Testbett-Projekt auch in Xcode öffnen, indem Sie Folgendes ausführen:

$ open app-testbed/iOSTestbed.xcodeproj

Dadurch können Sie die vollständige Xcode-Toolsuite zum Debugging verwenden.

Die zum Ausführen der Testsuite verwendeten Argumente sind Teil des Testplans definiert. Um den Testplan zu ändern, wählen Sie den Testplan-Knoten des Projektbaums aus (er sollte das erste Kind des Stammknotens sein) und wählen Sie die Registerkarte "Configurations". Ändern Sie den Wert "Arguments Passed On Launch", um die Testargumente zu ändern.

Der Testplan deaktiviert auch das parallele Testen und gibt die Verwendung der Datei Testbed.lldbinit für die Konfiguration des Debuggers an. Die Standard-Debuggerkonfiguration deaktiviert automatische Breakpoints bei den Signalen SIGINT, SIGUSR1, SIGUSR2 und SIGXFSZ.

7.3. App Store-Konformität

Der einzige Mechanismus zur Verteilung von Apps an externe iOS-Geräte ist die Einreichung der App im iOS App Store. Apps, die zur Verteilung eingereicht werden, müssen den App Review-Prozess von Apple bestehen. Dieser Prozess umfasst eine Reihe automatisierter Validierungsregeln, die das eingereichte Anwendungsbundle auf problematischen Code untersuchen. Es sind einige Schritte erforderlich, um sicherzustellen, dass Ihre App diese Validierungsschritte erfolgreich durchläuft.

7.3.1. Inkompatibler Code in der Standardbibliothek

Die Python-Standardbibliothek enthält Code, der bekanntermaßen gegen diese automatisierten Regeln verstößt. Obwohl diese Verstöße als Fehlalarme erscheinen, können die Überprüfungsregeln von Apple nicht angefochten werden. Daher ist es notwendig, die Python-Standardbibliothek zu ändern, damit eine App die Überprüfung durch den App Store besteht.

Der Python-Quellbaum enthält eine Patch-Datei, die allen Code entfernt, der bekanntermaßen Probleme mit dem App Store Review-Prozess verursacht. Dieser Patch wird automatisch beim Kompilieren für iOS angewendet.

7.3.2. Datenschutzerklärungen (Privacy Manifests)

Im April 2025 hat Apple eine Anforderung eingeführt, dass bestimmte Drittanbieterbibliotheken ein Privacy Manifest bereitstellen müssen. Wenn Sie also ein binäres Modul haben, das eine der betroffenen Bibliotheken verwendet, müssen Sie eine .xcprivacy-Datei für diese Bibliothek bereitstellen. OpenSSL ist eine Bibliothek, die von dieser Anforderung betroffen ist, aber es gibt auch andere.

Wenn Sie ein binäres Modul namens mymodule.so produzieren und das Xcode-Build-Skript wie in Schritt 7 oben beschrieben verwenden, können Sie eine mymodule.xcprivacy-Datei neben mymodule.so platzieren. Das Privacy Manifest wird an den erforderlichen Speicherort installiert, wenn das binäre Modul in ein Framework umgewandelt wird.