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 ではこの値となります。

Information about the specific runtime environment, including the iOS
version, device model, and whether the device is a simulator, can be
obtained using "platform.ios_ver()". "platform.system()" will report
"iOS" or "iPadOS", depending on the device.

"os.uname()" reports kernel-level details; it will report a name of
"Darwin".


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

The Python standard library has some notable omissions and
restrictions on iOS. See the API availability guide for iOS for
details.


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. Add a build step that processes the Python standard library, and
   your own Python binary dependencies. In the "Build Phases" tab, add
   a new "Run Script" build step *before* the "Embed Frameworks" step,
   but *after* the "Copy Bundle Resources" step. Name the step
   "Process Python libraries", disable the "Based on dependency
   analysis" checkbox, and set the script content to:

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

   If you have placed your XCframework somewhere other than the root
   of your project, modify the path to the first argument.

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

   * UTF-8 mode ("PyPreConfig.utf8_mode") is *enabled*;

   * Buffered stdio ("PyConfig.buffered_stdio") is *disabled*;

   * Writing bytecode ("PyConfig.write_bytecode") is *disabled*;

   * Signal handlers ("PyConfig.install_signal_handlers") are
     *enabled*;

   * System logging ("PyConfig.use_system_logger") is *enabled*
     (optional, but strongly recommended; this is enabled by default);

   * "PYTHONHOME" for the interpreter is configured to point at the
     "python" subfolder of your app's bundle; and

   * The "PYTHONPATH" for the interpreter includes:

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

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

     * アプリのバンドルの "app" サブフォルダ

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

Steps 7 and 8 of these instructions assume that you have a single
folder of pure Python application code, named "app". If you have
third-party binary modules in your app, some additional steps will be
required:

* You need to ensure that any folders containing third-party binaries
  are either associated with the app target, or are explicitly copied
  as part of step 7. Step 7 should also purge any binaries that are
  not appropriate for the platform a specific build is targeting
  (i.e., delete any device binaries if you're building an app
  targeting the simulator).

* If you're using a separate folder for third-party packages, ensure
  that folder is added to the end of the call to "install_python" in
  step 7, and as part of the "PYTHONPATH" configuration in step 8.

* If any of the folders that contain third-party packages will contain
  ".pth" files, you should add that folder as a *site directory*
  (using "site.addsitedir()"), rather than adding to "PYTHONPATH" or
  "sys.path" directly.


7.2.3. Testing a Python package
-------------------------------

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 <path/to/module1> --app <path/to/module2> app-testbed

Or, if you've sourced your own XCframework, by running:

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

Any folders specified with the "--app" flag will be copied into the
cloned testbed project. The resulting testbed will be created in the
"app-testbed" folder. In this example, the "module1" and "module2"
would be importable modules at runtime. If your project has additional
dependencies, they can be installed into the "app-
testbed/Testbed/app_packages" folder (using "pip install --target app-
testbed/Testbed/app_packages" or similar).

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. Incompatible code in the standard library
------------------------------------------------

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

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


7.3.2. Privacy manifests
------------------------

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.
