# 7. iOS で Python を使う

著者:

  Russell Keith-Magee (2024-03)

iOS における Pythonは、デスクトッププラットフォームにおける Python とは異なります。 デスクトッププラットフォームでは、 Python は一般的にコンピューターのどのユーザーでも使えるシステムリソースとしてインストールされます。そして、ユーザーは python 実行可能ファイルを実行して対話型プロンプトにコマンドを入力したり、 Python スクリプトを実行したりして、 Python を使用することができるのです。

iOS においては、システムリソースとしてのインストールという概念はありません。ソフトウェア配布が可能なのは、 "アプリ" だけです。また、 python 実行可能ファイルを実行したり、 Python の REPL を使用したりする、コンソールも存在しません。

このため、 Python を iOS 上で使うただ一つの方法は、埋め込みモード、つまり、ネイティブ iOS アプリケーションを書き、 `libPython` を使用して Python インタープリタを埋め込み、そして Python 埋め込み API を使用して Python コードを呼び出すことです。 それにより、完全な Python インタープリタ、標準ライブラリ、 及び Python のコードが、 iOS App Store を経由して配布可能なスタンドアローンなバンドルとしてパッケージ化されます。

もし、初めて iOS アプリを Python で書くことを試みているなら、 BeeWare や Kivy といったプロジェクトは、よりわかりやすいユーザー体験を提供するでしょう。これらのプロジェクトは iOS プロジェクトを実行することに関連する複雑なことを管理するので、あなたは Python のコードに集中するだけで良くなります。

## 7.1. iOS ランタイムでの Python

### 7.1.1. iOS のバージョン互換性

サポートされる最小の iOS バージョンは、コンパイル時に `configure` の `--host` オプションを使用して指定できます。デフォルトでは、 iOS 用にコンパイルする場合、 Python は最小で 13.0 の iOS バージョンをサポートするようにコンパイルされます。異なる最小 iOS バージョンを使用するには、 `--host` 引数の一部としてバージョン番号を提供します。例えば `--host=arm64-apple-ios15.4-simulator` とすると、 ARM64 シミュレーター用のビルドを Deployment Target 15.4 でコンパイルします。

### 7.1.2. プラットフォームの識別

iOS 上で実行している場合、 `sys.platform` は `ios` となります。アプリがシミュレーターで実行されているか、物理デバイスで実行されているかにかかわらず、 iPhone または iPad ではこの値となります。

iOS バージョンやデバイスのモデル、デバイスがシミュレーターであるかどうかを含めた、特定のランタイム環境についての情報は、 `platform.ios_ver()` を使用して取得できます。 `platform.system()` は、デバイスにより `iOS` または `iPadOS` を報告します。

`os.uname()` は、カーネルレベルの詳細を報告します。これは `Darwin` の名前を報告します。

### 7.1.3. 標準ライブラリの利用可能性

Python の 標準ライブラリには、 iOS におけるいくつかの重要な省略や制限があります。詳細は iOS 向けの API 利用可能性ガイド を参照してください。

### 7.1.4. バイナリ拡張モジュール

プラットフォームとしての iOS についての重要な違いの一つは、 App Store での配布がアプリケーションのパッケージングに厳しい条件を課すということです。 これらの条件の一つは、バイナリ拡張モジュールの配布方法を規定します。

iOS App Store では、 iOS アプリの全てのバイナリモジュールが、パッケージ化されたアプリの `Frameworks` フォルダに保存された、適切なメタデータ付きのフレームワークに含まれる動的ライブラリである必要があります。フレームワークごとにバイナリは一つだけで、 `Frameworks` フォルダの外に実行可能バイナリデータを設置することはできません。

これは、バイナリ拡張モジュールが `sys.path` 上のどの場所からでも読み込み可能な、通常の Python のバイナリ配布のアプローチと衝突します。 確実に App Store ポリシーに従うために、 iOS プロジェクトはいずれの Python パッケージにも、 `.so` バイナリモジュールを、個別の、スタンドアローンで、適切なメタデータと署名付きのフレームワークに変換する後処理を行わなければなりません。どのように後処理を行うかの詳細は、 プロジェクトに Python を追加する のガイドを参照してください。

Python が新しい場所にあるバイナリを見つけることを助けるために、 `sys.path` にある元の `.so` ファイルは `.fwork` ファイルに置き換えられます。 このファイルは、アプリバンドルからのレームワークバイナリの相対パスを含むテキストファイルです。フレームワークが元の場所に解決できるようにするためには、フレームワークは、アプリバンドルからの `.fwork` ファイルの相対パスが含まれた、 `.origin` ファイルを含む必要があります。

例えば、`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` モジュールは、元の場所で、 `Frameworks/foo.bar._whiz/foo.bar._whiz` のパスを含む `sources/foo/bar/_whiz.abi3.fwork` マーカーファイルに記述されます。 また、フレームワークは、 `.fwork` へのパスを含む `Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin` も含まなければなりません。

iOS 上で実行している場合、 Python インタープリタは `.fwork` ファイルを読み込んでインポートすることができる `AppleFrameworkLoader` をインストールします。インポートされると、バイナリモジュールの `__file__` 属性は `.fwork` ファイルの場所を返します。一方、読み込まれたモジュールの `ModuleSpec` はフレームワークフォルダのバイナリの場所として `origin` を返します。

### 7.1.5. コンパイラスタブバイナリ

Xcode は、 iOS 用の明示的なコンパイラを提供していません。代わりに、完全なコンパイラのパスを解決する `xcrun` スクリプトを使用します (たとえば `xcrun --sdk iphoneos clang` は iPhone デバイス用の `clang` を取得します) 。しかし、これは2つの問題を引き起こします:

- `xcrun` の出力はマシン固有のパスを含み、ユーザー間で共有できない sysconfig モジュールにつながり、

- これにより、 `CC`/`CPP`/`LD`/`AR` 定義にスペースが含まれることになります。多くの C エコシステムツールが、最初のスペースでコマンドラインを分割し、コンパイラ実行ファイルを取得できることを前提としています。しかし、 `xcrun` を使用する場合はそうではありません。

これらの問題を避けるため、 Python はこれらのツール用のスタブを提供しました。これらのスタブは、コンパイルされた iOS フレームワークとともに配布される `bin` フォルダで配布される、基礎の `xcrun` ツールのシェルスクリプトラッパーです。これらのスクリプトは再配置可能で、常に適切なローカルシステムパスに解決されます。これらのスクリプトをフレームワークを伴う bin フォルダーに含めることで、 `sysconfig` モジュールはエンドユーザーが自身のモジュールをコンパイルするのに有用になります。iOS 用の サードパーティの Python モジュールをコンパイルするときは、これらのスタブバイナリがパス上にあることを確認するべきです。

## 7.2. iOS での Python のインストール

### 7.2.1. iOS アプリビルド用のツール

iOS 向けのビルドには、 Apple の Xcode のツールを使用します。Xcode の最新の安定リリースを使用することを強く推奨します。Apple は古い macOS のバージョン向けには Xcode をメンテナンスしないため、これには最も (または二番目に) 最近にリリースされた macOS のバージョンが必要です。 Xcode コマンドラインツールは iOS 開発には不十分であり、完全な Xcode のインストールが必要です。

iOS シミュレーター上でコードを実行したい場合は、 iOS Simulator プラットフォームもインストールする必要があります。 Xcode を初めて実行したとき、 iOS Simulator プラットフォームを選択するプロンプトが表示されるはずです。代わりに、 Xcode の Settings パネルの Platforms タブから iOS Simulator プラットフォームを選択して追加することもできます。

### 7.2.2. iOS プロジェクトに Python を追加する

Python は、 Swift または Objective-C を使って、どの iOS プロジェクトにでも追加できます。以下の例では、 Objective-C を使用しています。 Swift を使う場合は、 PythonKit のようなライブラリが役に立つかもしれません。

Python を iOS Xcode プロジェクトに追加するには:

1. 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 Python `XCFramework`. At a minimum, you will need a build that supports `arm64-apple-ios`, plus one of either `arm64-apple-ios-simulator` or `x86_64-apple-ios-simulator`.

2. `XCframework` を iOS プロジェクトにドラッグします。以降の説明では、 プロジェクトのルートに `XCframework` を設置したものと仮定しますが、パスを必要に応じて調整することで、他の場所を使用することも出来ます。

3. アプリケーションのコードを、 Xcode プロジェクトにフォルダーとして追加します。以降の説明では、 プロジェクトのルートに `app` という名前のユーザーコードが入ったフォルダを設置したものと仮定しますが、パスを必要に応じて調整することで、他の場所を使用することも出来ます。フォルダがアプリのターゲットに関連付けられていることを確認してください。

4. Xcode プロジェクトのルートノードを選択することで、アプリのターゲットを選択してください。すると、ターゲット名がサイドバーに現れるはずです。

5. "General" の設定の "Frameworks, Libraries and Embedded Content" に、 "Embed & Sign" を選択して `Python.xcframework` を追加してください。

6. "Build Settings" タブで、次の項目を修正してください:

   - 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

7. Python の標準ライブラリおよび独自の Python バイナリ依存関係を処理するビルドステップを追加します。 "Build Phases" タブで、新しい "Run Script" ビルドステップを "Embed Frameworks" ステップの前に追加してください。ステップの名前は "Process Python libraries" にして、 "Based on dependency analysis" のチェックボックスを外し、スクリプトの内容を次のように設定してください:

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

   もし XCFramework をプロジェクトのルート以外のどこかに設置した場合は、一つ目の引数へのパスを修正してください。

8. Python インタープリタを埋め込みモードで初期化・使用する Objective C コードを追加します。次のことを確認する必要があります:

   - UTF-8 モード (`PyPreConfig.utf8_mode`) が 有効 になっていること

   - Buffered stdio (`PyConfig.buffered_stdio`) が 無効 になっていること

   - バイトコードの書き込み (`PyConfig.write_bytecode`) が 無効 になっていること

   - シグナルハンドラ (`PyConfig.install_signal_handlers`) が 有効 になっていること

   - システムログ (`PyConfig.use_system_logger`) が 有効 になっていること (オプションですが、強く推奨され、デフォルトで有効化されています)

   - インタープリタの `PYTHONHOME` がアプリバンドルの `python` サブフォルダを指すように構成されていること

   - インタープリタの `PYTHONPATH` が次のものを含むこと

     - アプリのバンドルの `python/lib/python3.X` サブフォルダ

     - アプリのバンドルの `python/lib/python3.X/lib-dynload` サブフォルダ

     - アプリのバンドルの `app` サブフォルダ

   アプリのバンドルの場所は `[[NSBundle mainBundle] resourcePath]` を用いて取得できます。

これらの説明の手順 7, 8 は `app` という名前のただ一つの純粋な Python アプリケーションのコードのフォルダがあることを前提としています。もしサードパーティのバイナリモジュールがアプリに含まれる場合は、いくつかの追加の手順が必要です:

- サードパーティのバイナリを含むフォルダが、アプリのターゲットに関連付けられている、または手順 7 の一部として明示的にコピーされていることを確認する必要があります。手順 7 では、特定のビルドがターゲットとするプラットフォームに適切でないバイナリを取り除く(つまり、もしシミュレーターをターゲットとするアプリをビルドしている場合は、デバイスバイナリを削除する)ことも必要です。

- サードパーティのパッケージに別のフォルダを使用している場合は、手順 7 でフォルダが `install_python` の呼び出しの末尾に追加され、手順 8 の `PYTHONPATH` 設定の一つとして追加されていることを確認してください。

- サードパーティパッケージを含むフォルダが `.pth` ファイルを含む場合、直接 `PYTHONPATH` や `sys.path` に追加するのではなく、そのフォルダを (`site.addsitedir()` を使用して) サイトディレクトリとして追加する必要があります。

### 7.2.3. 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 <module1 のパス> --app <module2 のパス> app-testbed

もしくは、独自の XCFramework を調達した場合は、次のコマンドを実行して行えます:

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

`--app` フラグで指定されたフォルダは、複製されたテストベッドプロジェクトにコピーされます。結果として得られるテストベッドは、 `app-testbed` フォルダの中に作成されます。この例では、 `module1` と `module2` が、実行時にインポート可能なモジュールとなります。プロジェクトに追加の依存関係がある場合は、 (`pip install --target app-testbed/Testbed/app_packages` をなど使用して) それらは `app-testbed/Testbed/app_packages` フォルダの中にインストールできます。

You can then use the `app-testbed` folder to run the test suite for your app, For example, if `module1.tests` was the entry point to your test suite, you could run:

    $ python app-testbed run -- module1.tests

This is the equivalent of running `python -m module1.tests` on a desktop Python build. Any arguments after the `--` will be passed to the testbed as if they were arguments to `python -m` on a desktop machine.

You can also open the testbed project in Xcode by running:

    $ open app-testbed/iOSTestbed.xcodeproj

This will allow you to use the full Xcode suite of tools for debugging.

The arguments used to run the test suite are defined as part of the test plan. To modify the test plan, select the test plan node of the project tree (it should be the first child of the root node), and select the "Configurations" tab. Modify the "Arguments Passed On Launch" value to change the testing arguments.

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. App Store コンプライアンス

The only mechanism for distributing apps to third-party iOS devices is to submit the app to the iOS App Store; apps submitted for distribution must pass Apple's app review process. This process includes a set of automated validation rules that inspect the submitted application bundle for problematic code. There are some steps that must be taken to ensure that your app will be able to pass these validation steps.

### 7.3.1. 標準ライブラリ内の互換性のないコード

Python の標準ライブラリには、これらの自動ルールに違反することが知られているコードがいくつか含まれています。これらの違反は誤検出であると思われますが、 Apple の審査ルールに異議を唱えることはできません。そのため、 Python の標準ライブラリを、アプリが App Store 審査に合格するよう修正する必要があります。

Python のソースツリーは、 App Store 審査プロセスで問題を引き起こすことが知られているすべてのコードを削除する パッチファイル を含んでいます。このパッチは iOS 用のビルド時に自動的に適用されます。

### 7.3.2. プライバシーマニフェスト

In April 2025, Apple introduced a requirement for certain third-party libraries to provide a Privacy Manifest. As a result, if you have a binary module that uses one of the affected libraries, you must provide an `.xcprivacy` file for that library. OpenSSL is one library affected by this requirement, but there are others.

If you produce a binary module named `mymodule.so`, and use you the Xcode build script described in step 7 above, you can place a `mymodule.xcprivacy` file next to `mymodule.so`, and the privacy manifest will be installed into the required location when the binary module is converted into a framework.