7. Python unter iOS verwenden¶
- Autoren:
Russell Keith-Magee (2024-03)
Python unter 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. Nutzer interagieren dann mit Python, indem sie eine ausführbare Datei python starten und Befehle an einer interaktiven Eingabeaufforderung eingeben, oder indem sie ein Python-Skript ausführen.
Unter iOS gibt es kein Konzept der Installation als Systemressource. Die einzige Verteilungseinheit für Software ist eine „App“. Es gibt auch keine Konsole, in der du eine ausführbare Datei python starten oder mit einem Python-REPL interagieren könntest.
Infolgedessen ist die einzige Möglichkeit, Python unter iOS zu verwenden, der eingebettete Modus – das heißt, indem du eine native iOS-Anwendung schreibst, einen Python-Interpreter mithilfe von libPython einbettest und Python-Code über die Python-Einbettungs-API aufrufst. Der vollständige Python-Interpreter, die Standardbibliothek und dein gesamter Python-Code werden dann als eigenständiges Paket gebündelt, das über den iOS App Store vertrieben werden kann.
If you’re looking to experiment for the first time with writing an iOS app in Python, projects such as BeeWare, Kivy, and Flet will provide a much more approachable user experience. These projects manage the complexities associated with getting an iOS project running, so you only need to deal with the Python code itself.
7.1. Python zur Laufzeit unter iOS¶
7.1.1. iOS-Versionskompatibilität¶
Die minimal unterstützte iOS-Version wird zur Kompilierzeit über die Option --host von configure festgelegt. Standardmäßig wird Python bei der Kompilierung für iOS mit einer minimal unterstützten iOS-Version von 13.0 kompiliert. Um eine andere minimale iOS-Version zu verwenden, gib 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. Plattform-Identifikation¶
Bei der Ausführung unter iOS gibt sys.platform den Wert ios zurück. Dieser Wert wird sowohl auf einem iPhone als auch auf einem iPad zurückgegeben, unabhängig davon, ob die App im Simulator oder auf einem physischen Gerät läuft.
Informationen zur konkreten Laufzeitumgebung, einschließlich der iOS-Version, des Gerätemodells und ob es sich 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 wird der Name Darwin gemeldet.
7.1.3. Verfügbarkeit der Standardbibliothek¶
Die Python-Standardbibliothek weist unter iOS einige wesentliche Auslassungen und Einschränkungen auf. Siehe den API-Verfügbarkeitsleitfaden für iOS für Details.
7.1.4. Binäre Erweiterungsmodule¶
Ein wesentlicher Unterschied bei iOS als Plattform ist, dass die Verteilung über den App Store strenge Anforderungen an die Paketierung einer Anwendung stellt. Eine dieser Anforderungen regelt, wie binäre Erweiterungsmodule verteilt werden.
Der iOS App Store verlangt, dass alle Binärmodule in einer iOS-App dynamische Bibliotheken sein müssen, die in einem Framework mit entsprechenden Metadaten enthalten sind und im Ordner Frameworks der gepackten App gespeichert werden. Pro Framework darf es nur eine einzige Binärdatei geben, und außerhalb des Ordners Frameworks darf sich kein ausführbares Binärmaterial befinden.
Dies steht im Widerspruch zum üblichen Python-Ansatz zur Verteilung von Binärdateien, der es erlaubt, ein binäres Erweiterungsmodul von einem beliebigen Ort in sys.path zu laden. Um die Einhaltung der App-Store-Richtlinien sicherzustellen, muss ein iOS-Projekt jedes Python-Paket nachbearbeiten und dabei .so-Binärmodule in einzelne, eigenständige Frameworks mit entsprechenden Metadaten und Signatur umwandeln. Details zur Durchführung dieser Nachbearbeitung findest du im Leitfaden zum Hinzufügen von Python zu deinem Projekt.
Damit Python die Binärdateien an ihrem neuen Speicherort finden kann, wird die ursprüngliche .so-Datei in 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 wieder zum ursprünglichen Speicherort zurückfinden kann, muss es eine .origin-Datei enthalten, die den Speicherort der .fwork-Datei relativ zum App-Bundle enthält.
Betrachte zum Beispiel den Fall eines Imports from foo.bar import _whiz, wobei _whiz mit dem Binärmodul sources/foo/bar/_whiz.abi3.so implementiert ist, wobei sources der auf sys.path registrierte Speicherort relativ zum Anwendungsbündel ist. Dieses Modul muss als Frameworks/foo.bar._whiz.framework/foo.bar._whiz verteilt werden (wobei der Framework-Name aus dem vollständigen Importpfad des Moduls gebildet wird), mit einer Info.plist-Datei im .framework-Verzeichnis, die die Binärdatei als Framework identifiziert. Das Modul foo.bar._whiz würde am ursprünglichen Speicherort durch eine Markierungsdatei sources/foo/bar/_whiz.abi3.fwork repräsentiert, die den Pfad Frameworks/foo.bar._whiz/foo.bar._whiz enthält. Das Framework würde außerdem Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin enthalten, welches den Pfad zur .fwork-Datei enthält.
Beim Ausführen unter iOS installiert der Python-Interpreter einen AppleFrameworkLoader, der in der Lage ist, .fwork-Dateien zu lesen und zu importieren. Nach dem Import gibt das Attribut __file__ des Binärmoduls den Speicherort der .fwork-Datei an. Die ModuleSpec des geladenen Moduls gibt jedoch als origin den Speicherort der Binärdatei im Framework-Ordner an.
7.1.5. Compiler-Stub-Binärdateien¶
Xcode stellt für iOS keine expliziten Compiler bereit. Stattdessen verwendet es ein xcrun-Skript, das zu einem vollständigen Compiler-Pfad aufgelöst wird (z. B. xcrun --sdk iphoneos clang, um den clang für ein iPhone-Gerät zu erhalten). Die Verwendung dieses Skripts bringt jedoch zwei Probleme mit sich:
Die Ausgabe von
xcrunenthält maschinenspezifische Pfade, was zu einem sysconfig-Modul führt, das nicht zwischen Benutzern geteilt werden kann, undDies führt zu
CC/CPP/LD/AR-Definitionen, die Leerzeichen enthalten. Es gibt viele Werkzeuge im C-Ökosystem, die davon ausgehen, dass man eine Befehlszeile am ersten Leerzeichen aufteilen kann, um den Pfad zur ausführbaren Compiler-Datei zu erhalten. Das ist bei der Verwendung vonxcrunnicht 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 bin-Ordner zusammen mit dem kompilierten iOS-Framework verteilt werden. Diese Skripte sind verschiebbar und lösen sich stets zu den passenden lokalen Systempfaden auf. Indem diese Skripte in den bin-Ordner aufgenommen werden, der ein Framework begleitet, wird der Inhalt des sysconfig-Moduls für Endnutzer nützlich, um ihre eigenen Module zu kompilieren. Beim Kompilieren von Python-Modulen von Drittanbietern für iOS solltest du sicherstellen, dass sich diese Stub-Binärdateien in deinem Pfad befinden.
7.2. Python unter iOS installieren¶
7.2.1. Werkzeuge zum Erstellen von iOS-Apps¶
Der Build-Prozess für iOS erfordert die Verwendung von Apples Xcode-Werkzeugen. Es wird dringend empfohlen, die aktuellste stabile Xcode-Version zu verwenden. Dies erfordert in der Regel die Verwendung der neuesten (oder zweit-neuesten) veröffentlichten 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. Du benötigst eine vollständige Xcode-Installation.
Wenn du deinen Code im iOS-Simulator ausführen möchtest, musst du außerdem eine iOS Simulator Platform installieren. Beim ersten Start von Xcode solltest du aufgefordert werden, eine iOS Simulator Platform auszuwählen. Alternativ kannst du eine iOS Simulator Platform über den Reiter „Platforms“ im Xcode-Einstellungsfenster hinzufügen.
7.2.2. Python zu einem iOS-Projekt hinzufügen¶
Python kann jedem iOS-Projekt hinzugefügt werden, sowohl mit Swift als auch mit Objective C. Die folgenden Beispiele verwenden Objective C. Wenn du Swift verwendest, könnte dir eine Bibliothek wie PythonKit hilfreich sein.
Um Python zu einem iOS-Xcode-Projekt hinzuzufügen:
Baue oder beziehe ein Python-
XCFramework. Wie du ein Python-XCFrameworkbaust, steht in der Anleitung unter Platforms/Apple/iOS/README.md (in der CPython-Quelldistribution). Mindestens brauchst du einen Build, derarm64-apple-iosunterstützt, dazu entwederarm64-apple-ios-simulatoroderx86_64-apple-ios-simulator.Zieh das
XCframeworkin dein iOS-Projekt. In der folgenden Anleitung nehmen wir an, dass du dasXCframeworkin die Wurzel deines Projekts gelegt hast. Du kannst aber jeden anderen Ort verwenden und die Pfade entsprechend anpassen.Füge deinen Anwendungscode als Ordner in dein Xcode-Projekt ein. In der folgenden Anleitung nehmen wir an, dass dein eigener Code in einem Ordner namens
appin der Wurzel deines Projekts liegt. Du kannst jeden anderen Ort verwenden und die Pfade entsprechend anpassen. Stelle sicher, dass dieser Ordner dem Ziel deiner App zugeordnet ist.Wähle das App-Ziel aus, indem du den Wurzelknoten deines Xcode-Projekts anklickst und dann den Zielnamen in der erscheinenden Seitenleiste.
Füge in den Einstellungen unter „General“ bei „Frameworks, Libraries and Embedded Content“ das
Python.xcframeworkhinzu, mit „Embed & Sign“ ausgewählt.Ändere im Reiter „Build Settings“ Folgendes:
Build Options
User Script Sandboxing: No
Enable Testability: Yes
Pfade suchen
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
Füge einen Build-Schritt hinzu, der die Python-Standardbibliothek und deine eigenen binären Python-Abhängigkeiten verarbeitet. Füge im Reiter „Build Phases“ einen neuen Build-Schritt „Run Script“ ein, und zwar vor dem Schritt „Embed Frameworks“, aber nach dem Schritt „Copy Bundle Resources“. Nenne den Schritt „Process Python libraries“, schalte das Kontrollkästchen „Based on dependency analysis“ ab und setze den Inhalt des Skripts auf:
set -e source $PROJECT_DIR/Python.xcframework/build/build_utils.sh install_python Python.xcframework app
Wenn du dein XCframework an einem anderen Ort als der Wurzel deines Projekts abgelegt hast, passe den Pfad im ersten Argument an.
Füge Objective-C-Code hinzu, um einen Python-Interpreter im eingebetteten Modus zu starten und zu verwenden. Du solltest sicherstellen, dass:
der UTF-8-Modus (
PyPreConfig.utf8_mode) eingeschaltet ist;die gepufferte Standard-Ein-/Ausgabe (
PyConfig.buffered_stdio) abgeschaltet ist;das Schreiben von Bytecode (
PyConfig.write_bytecode) abgeschaltet ist;die Signalbehandlungen (
PyConfig.install_signal_handlers) eingeschaltet sind;die Systemprotokollierung (
PyConfig.use_system_logger) eingeschaltet ist (optional, aber dringend empfohlen, standardmäßig eingeschaltet);PYTHONHOMEfür den Interpreter auf den Unterordnerpythondes Bündels deiner App zeigt, undder
PYTHONPATHfür den Interpreter Folgendes enthält:den Unterordner
python/lib/python3.Xdes Bündels deiner App,den Unterordner
python/lib/python3.X/lib-dynloaddes Bündels deiner App, undden Unterordner
appdes Bündels deiner App
Den Ort des Bündels deiner App kannst du über
[[NSBundle mainBundle] resourcePath]ermitteln.
Die Schritte 7 und 8 dieser Anleitung setzen voraus, dass du einen einzigen Ordner mit reinem Python-Anwendungscode namens app hast. Wenn deine App binäre Module von Dritten enthält, sind zusätzliche Schritte nötig:
Du musst sicherstellen, dass alle Ordner mit Binärdateien von Dritten entweder dem App-Ziel zugeordnet oder in Schritt 7 ausdrücklich mitkopiert werden. Schritt 7 sollte außerdem alle Binärdateien entfernen, die für die Zielplattform des jeweiligen Builds nicht passen (also etwa Gerätebinärdateien löschen, wenn du eine App für den Simulator baust).
Wenn du einen eigenen Ordner für Pakete von Dritten verwendest, stelle sicher, dass dieser Ordner am Ende des Aufrufs von
install_pythonin Schritt 7 und in derPYTHONPATH-Konfiguration in Schritt 8 ergänzt wird.Wenn einer der Ordner mit Paketen von Dritten
.pth-Dateien enthält, solltest du diesen Ordner als Site-Verzeichnis hinzufügen (mitsite.addsitedir()), statt ihn direkt zuPYTHONPATHodersys.pathhinzuzufügen.
7.2.3. Ein Python-Paket testen¶
Der CPython-Quellbaum enthält ein Testbed-Projekt, mit dem die CPython-Testsammlung im iOS-Simulator ausgeführt wird. Dieses Testbed lässt sich auch als Testbed-Projekt verwenden, um die Testsammlung deiner eigenen Python-Bibliothek unter iOS auszuführen.
Nachdem du ein iOS-XCFramework gebaut oder bezogen hast (Einzelheiten unter Platforms/Apple/iOS/README.md), erstelle einen Klon des Python-Testbed-Projekts für iOS. Wenn du das Build-Skript Platforms/Apple verwendet hast, um das XCframework zu bauen, kannst du Folgendes ausführen:
$ python cross-build/iOS/testbed clone --app <path/to/module1> --app <path/to/module2> app-testbed
Oder, wenn du dein eigenes XCframework beschafft hast, mit:
$ python Platforms/Apple/testbed clone --platform iOS --framework <path/to/Python.xcframework> --app <path/to/module1> --app <path/to/module2> app-testbed
Alle mit dem Schalter --app angegebenen Ordner werden in das geklonte Testbed-Projekt kopiert. Das entstehende Testbed liegt im Ordner app-testbed. In diesem Beispiel wären module1 und module2 zur Laufzeit importierbare Module. Hat dein Projekt weitere Abhängigkeiten, lassen sich diese in den Ordner app-testbed/Testbed/app_packages installieren (mit pip install --target app-testbed/Testbed/app_packages oder Ähnlichem).
Anschließend kannst du den Ordner app-testbed verwenden, um die Testsammlung deiner App auszuführen. Wenn zum Beispiel module1.tests der Einsprungpunkt deiner Testsammlung war, könntest du Folgendes ausführen:
$ python app-testbed run -- module1.tests
Das entspricht dem Aufruf von python -m module1.tests auf einem Desktop-Python-Build. Alle Argumente nach dem -- werden an das Testbed weitergegeben, als wären sie Argumente von python -m auf einem Desktop-Rechner.
Du kannst das Testbed-Projekt auch in Xcode öffnen:
$ open app-testbed/iOSTestbed.xcodeproj
Damit stehen dir die vollständigen Xcode-Werkzeuge zur Fehlersuche zur Verfügung.
Die Argumente, mit denen die Testsammlung ausgeführt wird, sind Teil des Testplans. Um den Testplan zu ändern, wähle den Testplan-Knoten im Projektbaum (er sollte das erste Kind des Wurzelknotens sein) und dann den Reiter „Configurations“. Ändere den Wert „Arguments Passed On Launch“, um die Testargumente anzupassen.
Der Testplan schaltet außerdem paralleles Testen ab und gibt die Verwendung der Datei Testbed.lldbinit für die Konfiguration des Debuggers vor. Die Standardkonfiguration des Debuggers schaltet automatische Haltepunkte bei den Signalen SIGINT, SIGUSR1, SIGUSR2 und SIGXFSZ ab.
7.3. Vorgaben des App Store erfüllen¶
Der einzige Weg, Apps an fremde iOS-Geräte zu verteilen, ist die Einreichung im iOS App Store. Zur Verteilung eingereichte Apps müssen Apples App-Prüfverfahren durchlaufen. Dieses Verfahren umfasst eine Reihe automatischer Prüfregeln, die das eingereichte Anwendungsbündel auf bedenklichen Code untersuchen. Es gibt einige Schritte, die nötig sind, damit deine App diese Prüfungen besteht.
7.3.1. Unverträglicher Code in der Standardbibliothek¶
Die Python-Standardbibliothek enthält Code, von dem bekannt ist, dass er gegen diese automatischen Regeln verstößt. Diese Verstöße scheinen zwar Fehlalarme zu sein, Apples Prüfregeln lassen sich aber nicht anfechten. Deshalb ist es nötig, die Python-Standardbibliothek zu verändern, damit eine App die Prüfung des App Store besteht.
Der Python-Quellbaum enthält eine Patch-Datei, die allen Code entfernt, von dem bekannt ist, dass er im Prüfverfahren des App Store Schwierigkeiten macht. Dieser Patch wird beim Bauen für iOS automatisch angewandt.
7.3.2. Datenschutzmanifeste¶
Im April 2025 hat Apple die Anforderung eingeführt, dass bestimmte Bibliotheken von Dritten ein Privacy Manifest bereitstellen müssen. Wenn du also ein binäres Modul hast, das eine der betroffenen Bibliotheken verwendet, musst du für diese Bibliothek eine Datei .xcprivacy bereitstellen. OpenSSL ist eine der betroffenen Bibliotheken, es gibt aber weitere.
Wenn du ein binäres Modul namens mymodule.so erzeugst und das in Schritt 7 beschriebene Xcode-Build-Skript verwendest, kannst du eine Datei mymodule.xcprivacy neben mymodule.so legen — das Datenschutzmanifest wird dann beim Umwandeln des binären Moduls in ein Framework an den erforderlichen Ort installiert.