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 を経由し
て配布可能なスタンドアローンなバンドルとしてパッケージ化されます。

Python で iOS アプリを書くのを初めて試す場合は、BeeWare、Kivy、Flet な
どのプロジェクトを使うと、はるかに取り組みやすくなります。 これらのプ
ロジェクトが 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" ファイルに置き換えられます。 このテキストフ
ァイルには、アプリバンドルからの相対パスでフレームワークのバイナリの場
所が記載されています。 フレームワークから元の場所を逆に解決できるよう
にするため、フレームワークには ".origin" ファイルを含める必要がありま
す。 このファイルには、アプリバンドルからの相対パスで ".fwork" ファイ
ルの場所を記載します。

例えば、"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. Python の "XCFramework" をビルドするか、入手します。 Python の
   "XCFramework" をビルドする方法については、CPython のソース配布物に
   ある Platforms/Apple/iOS/README.md の手順を参照してください。 少な
   くとも、"arm64-apple-ios" と、"arm64-apple-ios-simulator" または
   "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" タブで、"Embed
   Frameworks" ステップの *前*、かつ "Copy Bundle Resources" ステップ
   の *後* に、新しい "Run Script" ビルドステップを追加します。 ステッ
   プに "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 パッケージのテスト
--------------------------------

CPython ソースツリーには、iOS シミュレータ上で CPython のテストスイー
トを実行するための テストベッドプロジェクト が含まれています。 このテ
ストベッドは、自分の Python ライブラリのテストスイートを iOS 上で実行
するためのテストベッドプロジェクトとしても使用できます。

iOS 用の XCFramework をビルドするか入手したら
(Platforms/Apple/iOS/README.md を参照)、Python の iOS テストベッドプロ
ジェクトのクローンを作成します。 "Platforms/Apple" ビルドスクリプトを
使って XCframework をビルドした場合は、次のコマンドを実行できます:

   $ python cross-build/iOS/testbed clone --app <module1 のパス> --app <module2 のパス> app-testbed

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

   $ python Platforms/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" フォルダの中にインストールできます。

これで、"app-testbed" フォルダを使ってアプリのテストスイートを実行でき
ます。 例えば、テストスイートのエントリーポイントが "module1.tests" で
あれば、次のコマンドを実行できます:

   $ python app-testbed run -- module1.tests

これは、デスクトップ用の Python ビルドで "python -m module1.tests" を
実行することに相当します。 "--" の後にある引数は、デスクトップマシンで
"python -m" に渡す引数と同じようにテストベッドに渡されます。

次のコマンドを実行して、テストベッドプロジェクトを Xcode で開くことも
できます:

   $ open app-testbed/iOSTestbed.xcodeproj

これにより、デバッグに Xcode のすべてのツールを使用できます。

テストスイートを実行するための引数は、テストプランの一部として定義され
ています。 テストプランを変更するには、プロジェクトツリーのテストプラ
ンノード (ルートノードの最初の子ノードのはずです) を選択し、
"Configurations" タブを選択します。 "Arguments Passed On Launch" の値
を変更すると、テストの引数を変更できます。

テストプランでは並列テストも無効にし、デバッガの設定に
"Testbed.lldbinit" ファイルを使うように指定しています。 デフォルトのデ
バッガ設定では、"SIGINT"、"SIGUSR1"、"SIGUSR2"、"SIGXFSZ" シグナルによ
る自動ブレークポイントを無効にしています。


7.3. App Store コンプライアンス
===============================

第三者の iOS デバイスにアプリを配布するには、iOS App Store にアプリを
提出するしかありません。 配布のために提出したアプリは、Apple のアプリ
審査に合格する必要があります。 この審査には、提出されたアプリケーショ
ンバンドル内に問題のあるコードがないかを調べる、一連の自動検証ルールが
含まれています。 アプリがこの検証に合格できるようにするには、いくつか
の手順を実施する必要があります。


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

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

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


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

2025 年 4 月、Apple は 特定のサードパーティライブラリにプライバシーマ
ニフェストの提供を義務付けました。 そのため、対象となるライブラリを使
うバイナリモジュールがある場合は、そのライブラリ用の ".xcprivacy" ファ
イルを提供する必要があります。 OpenSSL はこの要件の対象となるライブラ
リの一つですが、ほかにも対象のライブラリがあります。

"mymodule.so" という名前のバイナリモジュールを作成し、上記の手順 7 で
説明した Xcode ビルドスクリプトを使用する場合は、"mymodule.so" と同じ
場所に "mymodule.xcprivacy" ファイルを配置できます。 バイナリモジュー
ルをフレームワークに変換する際に、プライバシーマニフェストが必要な場所
にインストールされます。
