iOSのSwift,Objective-CでPythonを呼び出す


iOSでは使えないPython

MacにはPythonが標準で入っているので、Cocoaアプリケーションではlibpythonが簡単に使えますが、iOSにはPythonが入ってないので使うことができません。
いくら調べてもPythonistaのことばっかりで嫌になります。
そんなところ、libpythonを静的ライブラリにしてiOSアプリに組み込んで動かすことができたので、ここで書くのはその方法。

kivy

kivyという、Pythonでマルチタッチアプリケーションを作成するツールがあり、これはiOSに対応しています。
Pythonのソースコードからライブラリをビルドするのは難しかったので、こいつからlibpythonだけを抜き出して使います。
kivyではPython2.7とPython3.7をサポートしています。ここでは3.7を利用しますが、2.7も基本的には同じやり方でできるはずなので、適宜読み替えてください。

kivyを使えるようにする

Python3を標準に

Python2.7にしたい場合はスキップしてください
python3が入っていない場合はhomebrewなどを使ってインストールしてください

alias python=python3
alias pip=pip3

kivyをダウンロードと必要なもののインストール

git clone git://github.com/kivy/kivy-ios
cd kivy-ios
pip install -r requirements.txt
xcode-select --install
brew install autoconf automake libtool pkg-config
brew link libtool
sudo pip3 install Cython==0.28.1

ビルド

./toolchain.py build python3 
./toolchain.py build python3 hostpython 

これでPythonのビルドは完了です。
kivy-ios/dist/lib/内に

  • libcrypto.a
  • libffi.a
  • libpython3.a
  • libssl.a

が生成されているはずです。

プロジェクトにライブラリをインポート

必要なライブラリを追加

"Linked Frameworks and Libraries"に以下の3つを追加してください

  • libz.tbd
  • libbz2.tbd
  • libsqlite3.tbd

ビルドしたライブラリのインポート

Project NavigatorにFrameworksというグループがあるはずなので、kivy-ios/dist/lib/内にある4つのファイルをFinderからドラッグしてFrameworksグループに入れてください。
ダイアログが出てきたら"Copy items if needed"にチェックを入れて"Finish"をクリック。

python3をプロジェクトにコピー

kivy-ios/dist/root/python3がpython3の本体みたいなので、これをProject Navigatorにドラッグして、ダイアログが出てきたら"Copy items if needed"と"Create folder references"にチェックを入れて"Finish"をクリック。
Project Navigatorでpython3のフォルダが青いアイコンならOKです

Header Search Pathsを指定

このままではPythonのヘッダをコンパイラが見つけてくれないので、指定します。
Build Settings内のHeader Search Pathsにpython3をrecursiveで追加します

最終的にこうなっているはず


Objective-Cで使用

テストしてないです。

インクルード

使いたいクラスのヘッダで#include "Python.h"

PYTHONHOMEの指定

Py_Initializeをする前にPythonの標準ライブラリが入っているパス(PYTHON_HOME)を指定しないと標準ライブラリが使えません。なので、下にあるコードをPy_Initializeの前に実行してください。

NSString *resourcePath = [NSString stringWithFormat:@"%@/python3", [[NSBundle mainBundle] resourcePath], nil];
NSString *python_home = [NSString stringWithFormat:@"PYTHONHOME=%@", resourcePath, nil];
putenv((char *)[python_home UTF8String]);

実行

他のアプリケーションへの Python の埋め込み
あとはここに書いてある通りにすればいいです。

Swiftで使用

インクルード

Bridging-Headerが必要です。
ない場合は自分で作ってBuild Settingsで指定するか、新しい"C File"を生成すると自動で生成してくれるのでそれを使います。

Bridging-Headerさえ用意できれば、そこに#include "Python.h"と書くだけです。

PYTHONHOMEの指定

Py_Initializeをする前にPythonの標準ライブラリが入っているパス(PYTHON_HOME)を指定しないと標準ライブラリが使えません。なので、下にあるコードをPy_Initializeの前に実行してください。

let resourcePath = Bundle.main.resourcePath! + "/python3"
let python_home = "PYTHONHOME=\(resourcePath)" as NSString
putenv(UnsafeMutablePointer(mutating: python_home.utf8String))

実行

他のアプリケーションへの Python の埋め込み
あとはここに書いてあるみたいにできます。

ただし、1つ注意点があり、Swiftでは#defineによるマクロが使えないので、#defineで定義された関数が使えません。
例えば、
PyRun_SimpleString
#define PyRun_SimpleString(s) PyRun_SimpleStringFlags(s, NULL)
として定義されている関数なので、使うことができません。こういうときは、直接
PyRun_SimpleStringFlags(s, NULL)を呼び出す必要があります。

こういうのは超高水準レイヤのような場所で調べたり、ヘッダーファイル内を関数名で検索すればわかりますが、面倒ですね。もしSwiftでライブラリ内のマクロ関数を使う方法を知っている方いたら教えて欲しいです。

サンプル

標準ライブラリが使えます。
でも、これだとまだpythonを実行するだけで、Swiftとの値のやりとりができてない。
あと、kivyのrecipesを見ると、標準ライブラリ以外にもいくつかライブラリが使えそうだけど、それもまだ試してない。
とりあえず、進捗があったらまた記事を上げます。