7. Använda Python på iOS¶
- Författare:
Russell Keith-Magee (2024-03)
Python på iOS är olikt Python på stationära plattformar. På en stationär plattform installeras Python vanligtvis som en systemresurs som kan användas av alla användare av den datorn. Användare interagerar sedan med Python genom att köra en körbar python och ange kommandon i en interaktiv prompt, eller genom att köra ett Python-skript.
På iOS finns det inget koncept för installation som en systemresurs. Den enda enheten för distribution av programvara är en ”app”. Det finns inte heller någon konsol där du kan köra en python-körbar fil eller interagera med en Python REPL.
Som ett resultat är det enda sättet du kan använda Python på iOS i inbäddat läge - det vill säga genom att skriva en inbyggd iOS-applikation och bädda in en Python-tolk med hjälp av libPython
och anropa Python-kod med hjälp av Python embedding API. Den fullständiga Python-tolken, standardbiblioteket och all din Python-kod paketeras sedan som ett fristående paket som kan distribueras via iOS App Store.
Om du vill experimentera för första gången med att skriva en iOS-app i Python, kommer projekt som BeeWare och Kivy att ge en mycket mer lättillgänglig användarupplevelse. Dessa projekt hanterar de komplexiteter som är förknippade med att få ett iOS-projekt att köra, så att du bara behöver hantera själva Python-koden.
7.1. Python i körtid på iOS¶
7.1.1. kompatibilitet med iOS-versioner¶
Den minsta iOS-versionen som stöds anges vid kompileringstillfället med hjälp av alternativet --host
till configure
. När Python kompileras för iOS kommer det som standard att kompileras med en minsta iOS-version som stöds på 13.0. För att använda en annan minsta iOS-version, ange versionsnumret som en del av --host
-argumentet - till exempel, --host=arm64-apple-ios15.4-simulator
skulle kompilera en ARM64-simulatorbyggnad med ett implementeringsmål på 15.4.
7.1.2. Identifiering av plattform¶
Vid exekvering på iOS kommer sys.platform
att rapportera som ios
. Detta värde kommer att returneras på en iPhone eller iPad, oavsett om appen körs på simulatorn eller en fysisk enhet.
Information om den specifika körtidsmiljön, inklusive iOS-versionen, enhetsmodellen och om enheten är en simulator, kan erhållas med platform.ios_ver()
. platform.system()
rapporterar iOS
eller iPadOS
, beroende på enheten.
os.uname()
rapporterar detaljer på kärnnivå; den kommer att rapportera ett namn på Darwin
.
7.1.3. Tillgänglighet för standardbibliotek¶
Pythons standardbibliotek har några anmärkningsvärda utelämnanden och begränsningar på iOS. Se API availability guide for iOS för mer information.
7.1.4. Binära tilläggsmoduler¶
En märkbar skillnad med iOS som plattform är att distributionen via App Store ställer hårda krav på paketeringen av en applikation. Ett av dessa krav reglerar hur binära tilläggsmoduler distribueras.
IOS App Store kräver att alla binära moduler i en iOS-app måste vara dynamiska bibliotek, som ingår i ett ramverk med lämpliga metadata, som lagras i mappen Frameworks
i den paketerade appen. Det får bara finnas en enda binär modul per ramverk och det får inte finnas något körbart binärt material utanför mappen ”Frameworks”.
Detta strider mot den vanliga Python-metoden för distribution av binära filer, som gör det möjligt att ladda en binär tilläggsmodul från valfri plats på sys.path
. För att säkerställa efterlevnad av App Store-policyer måste ett iOS-projekt efterbehandla alla Python-paket och konvertera binära moduler av typen .so
till enskilda fristående ramverk med lämpliga metadata och signering. Mer information om hur du utför denna efterbearbetning finns i guiden för addding Python to your project.
För att hjälpa Python att upptäcka binärfiler på deras nya plats ersätts den ursprungliga filen .so
på sys.path
med en fil .fwork
. Den här filen är en textfil som innehåller platsen för ramverkets binärfil i förhållande till appbunten. För att ramverket ska kunna återgå till den ursprungliga platsen måste ramverket innehålla en fil med namnet .origin
som innehåller platsen för filen .fwork
i förhållande till appbunten.
Tänk till exempel på fallet med en import from foo.bar import _whiz
, där _whiz
implementeras med den binära modulen sources/foo/bar/_whiz.abi3.so
, där sources
är den plats som registrerats på sys.path
, relativt programpaketet. Den här modulen måste distribueras som Frameworks/foo.bar._whiz.framework/foo.bar._whiz
(ramverkets namn skapas från modulens fullständiga importsökväg), med en Info.plist
-fil i katalogen .framework
som identifierar binärfilen som ett ramverk. Modulen foo.bar._whiz
skulle representeras på den ursprungliga platsen med en källor/foo/bar/_whiz.abi3.fwork
markörfil, som innehåller sökvägen Frameworks/foo.bar._whiz/foo.bar._whiz
. Ramverket skulle också innehålla Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin
, som innehåller sökvägen till filen .fwork
.
När du kör på iOS kommer Python-tolken att installera en AppleFrameworkLoader
som kan läsa och importera .fwork
-filer. När den har importerats kommer attributet __file__
i den binära modulen att rapportera platsen för filen .fwork
. Men ModuleSpec
för den inlästa modulen kommer att rapportera origin
som platsen för den binära filen i ramverksmappen.
7.1.5. Kompilatorns stubbinaries¶
Xcode exponerar inte explicita kompilatorer för iOS, utan använder istället ett xcrun
-skript som löser till en fullständig kompilatorväg (t.ex. xcrun --sdk iphoneos clang
för att få clang
för en iPhone-enhet). Användningen av detta skript medför dock två problem:
Utdata från
xcrun
innehåller sökvägar som är maskinspecifika, vilket resulterar i en sysconfig-modul som inte kan delas mellan användare; ochDet resulterar i
CC
/CPP
/LD
/AR
-definitioner som innehåller mellanslag. Det finns många verktyg för C-ekosystem som förutsätter att du kan dela en kommandorad vid det första mellanslaget för att få sökvägen till kompilatorns körbara fil; detta är inte fallet när du använderxcrun
.
För att undvika dessa problem tillhandahöll Python stubbar för dessa verktyg. Dessa stubbar är skalskriptomslag runt de underliggande xcrun
-verktygen, som distribueras i en bin
-mapp som distribueras tillsammans med det kompilerade iOS-ramverket. Dessa skript är flyttbara och kommer alltid att lösa upp till lämpliga lokala systemsökvägar. Genom att inkludera dessa skript i bin-mappen som följer med ett ramverk blir innehållet i modulen sysconfig
användbart för slutanvändare som vill kompilera sina egna moduler. När du kompilerar Python-moduler från tredje part för iOS bör du se till att dessa stubbinaries finns på din sökväg.
7.2. Installera Python på iOS¶
7.2.1. Verktyg för att bygga iOS-appar¶
För att bygga för iOS krävs att du använder Apples Xcode-verktyg. Vi rekommenderar starkt att du använder den senaste stabila versionen av Xcode. Detta kräver att du använder den senaste (eller näst senaste) macOS-versionen, eftersom Apple inte underhåller Xcode för äldre macOS-versioner. Xcode Command Line Tools är inte tillräckligt för iOS-utveckling; du behöver en fullständig Xcode-installation.
Om du vill köra din kod på iOS-simulatorn måste du också installera en iOS Simulator Platform. Du bör uppmanas att välja en iOS Simulator Platform när du först kör Xcode. Alternativt kan du lägga till en iOS-simulatorplattform genom att välja på fliken Plattformar i panelen Inställningar i Xcode.
7.2.2. Lägga till Python i ett iOS-projekt¶
Python kan läggas till i alla iOS-projekt med hjälp av antingen Swift eller Objective C. I följande exempel används Objective C; om du använder Swift kan du ha nytta av ett bibliotek som PythonKit.
Så här lägger du till Python i ett iOS Xcode-projekt:
Bygg eller hämta ett Python
XCFramework
. Se instruktionerna i iOS/README.rst (i källdistributionen för CPython) för detaljer om hur man bygger ett PythonXCFramework
. Du behöver åtminstone ett bygge som stöderarm64-apple-ios
, plus en av antingenarm64-apple-ios-simulator
ellerx86_64-apple-ios-simulator
.Dra
XCframework
in i ditt iOS-projekt. I följande instruktioner antar vi att du har släpptXCframework
i roten till ditt projekt; du kan dock använda vilken annan plats du vill genom att justera sökvägar efter behov.Dra filen
iOS/Resources/dylib-Info-template.plist
till ditt projekt och se till att den är associerad med appmålet.Lägg till din applikationskod som en mapp i ditt Xcode-projekt. I följande instruktioner antar vi att din användarkod finns i en mapp med namnet
app
i roten av ditt projekt; du kan använda någon annan plats genom att justera sökvägar efter behov. Se till att den här mappen är kopplad till ditt appmål.Välj appens mål genom att välja rotnoden i ditt Xcode-projekt och sedan målnamnet i sidofältet som visas.
I inställningarna ”General”, under ”Frameworks, Libraries and Embedded Content”, lägg till
Python.xcframework
, med ”Embed & Sign” markerat.På fliken ”Build Settings” ändrar du följande:
Byggalternativ
Sandbox för användarskript: Nej
Aktivera testbarhet: Ja
Sökvägar
Sökvägar för ramverk:
$(PROJECT_DIR)
Sökvägar för rubriker:
"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"
Apple Clang - Varningar - Alla språk
Quoted inkluderas i ramverkets huvud: Nej
Lägg till ett byggsteg som kopierar Pythons standardbibliotek till din app. På fliken ”Build Phases” lägger du till ett nytt byggsteg ”Run Script” före steget ”Embed Frameworks”, men efter steget ”Copy Bundle Resources”. Döp steget till ”Install Target Specific Python Standard Library”, avmarkera kryssrutan ”Based on dependency analysis” och ställ in skriptinnehållet till:
ställa in -e mkdir -p "$CODESIGNING_FOLDER_PATH/python/lib" if [ "$EFFECTIVE_PLATFORM_NAME" = "-iphonesimulator" ]; then echo "Installerar Python-moduler för iOS-simulatorn" rsync -au --delete "$PROJECT_DIR/Python.xcframework/ios-arm64_x86_64-simulator/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" annat echo "Installerar Python-moduler för iOS-enhet" rsync -au --delete "$PROJECT_DIR/Python.xcframework/ios-arm64/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" fi
Observera att namnet på simulatorns ”slice” i XCframework kan vara annorlunda beroende på vilka CPU-arkitekturer som stöds av ditt
XCFramework
.Lägg till ett andra byggsteg som bearbetar de binära tilläggsmodulerna i standardbiblioteket till ”Framework”-format. Lägg till ett byggsteg för ”Run Script” direkt efter det som du lade till i steg 8, med namnet ”Prepare Python Binary Modules”. Det ska också ha ”Based on dependency analysis” avmarkerat, med följande skriptinnehåll:
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 "{}" \;
Lägg till Objective C-kod för att initiera och använda en Python-tolk i inbäddat läge. Du bör se till att:
UTF-8-läget (
PyPreConfig.utf8_mode
) är aktiverat;Buffered stdio (
PyConfig.buffered_stdio
) är inaktiverad;Skrivning av bytecode (
PyConfig.write_bytecode
) är inaktiverad;Signalhanterare (
PyConfig.install_signal_handlers
) är aktiverade;Systemloggning (
PyConfig.use_system_logger
) är aktiverad (valfritt, men rekommenderas starkt; detta är aktiverat som standard);
PYTHONHOME
för tolken är konfigurerad att peka på undermappenpython
i din apps paket; och
PYTHONPATH
för tolken inkluderar:
undermappen
python/lib/python3.X
i din apps paket,undermappen
python/lib/python3.X/lib-dynload
i din apps paket, ochundermappen
app
i din apps paketAppens bundle-plats kan bestämmas med hjälp av
[[NSBundle mainBundle] resourcePath]
.
Steg 8, 9 och 10 i dessa instruktioner förutsätter att du har en enda mapp med ren Python-applikationskod, som heter app
. Om du har binära moduler från tredje part i din app krävs ytterligare några steg:
Du måste se till att alla mappar som innehåller binärfiler från tredje part antingen är associerade med appens mål eller kopieras in som en del av steg 8. I steg 8 bör du också rensa bort alla binärfiler som inte är lämpliga för den plattform som en specifik build är inriktad på (dvs. ta bort alla enhetsbinärfiler om du bygger en app som är inriktad på simulatorn).
Alla mappar som innehåller binärfiler från tredje part måste bearbetas till ramverksform i steg 9. Anropet av
install_dylib
som bearbetar mappenlib-dynload
kan kopieras och anpassas för detta ändamål.Om du använder en separat mapp för tredjepartspaket ska du se till att den mappen ingår som en del av
PYTHONPATH
-konfigurationen i steg 10.Om någon av mapparna som innehåller tredjepartspaket kommer att innehålla
.pth
-filer bör du lägga till den mappen som en site-katalog (medsite.addsitedir()
), i stället för att lägga till den direkt iPYTHONPATH
ellersys.path
.
7.2.3. Testa ett Python-paket¶
CPythons källträd innehåller ett testbäddsprojekt som används för att köra CPythons testsvit på iOS-simulatorn. Denna testbädd kan också användas som ett testbäddsprojekt för att köra ditt Python-biblioteks testsvit på iOS.
När du har byggt eller hämtat ett iOS XCFramework (se iOS/README.rst för detaljer) skapar du en klon av Python iOS-testbäddsprojektet genom att köra:
$ python iOS/testbed clone --framework <path/to/Python.xcframework> --app <path/to/module1> --app <path/to/module2> app-testbed
Du måste ändra iOS/testbed
-referensen så att den pekar på den katalogen i CPythons källträd; alla mappar som anges med flaggan --app
kommer att kopieras till det klonade testbäddsprojektet. Den resulterande testbädden kommer att skapas i mappen app-testbed
. I det här exemplet skulle modul1
och modul2
vara importerbara moduler vid körning. Om ditt projekt har ytterligare beroenden kan de installeras i mappen app-testbed/iOSTestbed/app_packages
(med hjälp av pip install --target app-testbed/iOSTestbed/app_packages
eller liknande).
Du kan sedan använda mappen app-testbed
för att köra testsviten för din app, Till exempel, om module1.tests
var ingångspunkten till din testsvit, kan du köra:
$ python app-testbed run -- module1.tests
Detta motsvarar att köra python -m module1.tests
på en Python-byggd på skrivbordet. Alla argument efter --
kommer att skickas till testbädden som om de vore argument till python -m
på en skrivbordsmaskin.
Du kan också öppna testbäddsprojektet i Xcode genom att köra:
$ öppna app-testbed/iOSTestbed.xcodeproj
Detta gör att du kan använda hela Xcode-sviten med verktyg för felsökning.
Argumenten som används för att köra testsuiten definieras som en del av testplanen. För att ändra testplanen, välj testplanens nod i projektträdet (den bör vara det första underordnade objektet till rotnoden) och välj fliken ”Konfigurationer”. Ändra värdet för ”Argument som skickas vid start” för att ändra testargumenten.
Testplanen inaktiverar även parallella tester och anger att filen iOSTestbed.lldbinit
ska användas för att konfigurera felsökaren. Standardkonfigurationen för felsökaren inaktiverar automatiska brytpunkter för signalerna SIGINT
, SIGUSR1
, SIGUSR2
och SIGXFSZ
.
7.3. App Store-överensstämmelse¶
Den enda mekanismen för att distribuera appar till iOS-enheter från tredje part är att skicka in appen till iOS App Store; appar som skickas in för distribution måste passera Apples appgranskningsprocess. Denna process innehåller en uppsättning automatiserade valideringsregler som inspekterar det inlämnade applikationspaketet för problematisk kod.
Pythons standardbibliotek innehåller viss kod som är känd för att bryta mot dessa automatiserade regler. Även om dessa överträdelser verkar vara falskt positiva kan Apples granskningsregler inte ifrågasättas, så det är nödvändigt att modifiera Python-standardbiblioteket för att en app ska klara App Store-granskningen.
Pythons källkodsträd innehåller en patchfil som tar bort all kod som är känd för att orsaka problem med App Stores granskningsprocess. Denna patch tillämpas automatiskt när du bygger för iOS.