euphonictechnologies’s diary

Haskell超初心者の日記です。OCamlが好きです。

follow us in feedly

iOSアプリとLinuxコマンドラインアプリケーション間でライブラリを共有する

サーバサイドSwiftって全然盛り上がってないですね

なんかフロントエンド盛り上がってますがサーバサイドっていまいちそういう感じしませんね。悲しいです。フロントエンドアプリばっかり作ってるとフロントエンド脳になってまうで。

というわけで拙作のオセロアプリは着々と進捗があるのですが、ここ1年ほどアップデートをしておりません。iOS側のオセロアプリとサーバーサイドアプリケーションで同じオセロAIエンジンを共有したい、という文脈でどうしてもサーバサイドとフロントエンドでライブラリを共有したいのです。

iOSアプリとLinuxで動くコマンドアプリケーションでコードをシェアする方法…正攻法で挑んでみる

なんとなくググってみたのですが、いまいち決定打みたいなのはまだない・・・ように感じます。おそらくSwiftをヘビーに使っている会社の社内的なノウハウみたいなものはもうあるんでしょうが、あまり公開情報が引っかかりませんでした。なので正攻法で挑んでみようと思いました。

今回のゴールの図

では、iOSのSingleViewApplicationとUbuntuのコマンドラインアプリケーションでライブラリを共有します。こんなアプリです。

f:id:euphonictechnologies:20161024222651g:plain

…アプリってほどのものか?あくまでexampleですから。

ボタンを押すと次々乱数を表示します。それだけです。便利ですね!

んで、その擬似乱数の生成器が共有部分になっていて、コマンドラインアプリケーション側はこんな感じで使います。

f:id:euphonictechnologies:20161024223126g:plain

昔懐かし数あてゲームです。これをiOSで作るのは面倒だったのでiOS側はボタンを押して乱数を表示するだけのアプリになりまして、すみませんすみません。

というわけで、例にしては意外に応用しがいのあるユースケースじゃないかなーと。

今回の流れ

結構ありがちな流れとして(たまたま自分がそうだっていうだけですが)

  • iOSでアプリをもりもり作ってきて、手元に動くアプリがある
  • その一部をライブラリとして切り出したい
  • でもってそのライブラリをサーバサイドで使えるようにしたい

という状況を想定しております。なので今回の説明も

  • まずは完動品のiOSアプリを作ります
  • そしてライブラリを切り出します
  • それをサーバサイドのコマンドラインアプリで使います

という流れで行こうと思います。

まずはiOSアプリ側で乱数表示アプリを完成させる

というわけでMac上で普通にXcodeを使ってSingleViewApplicationを作ります。

f:id:euphonictechnologies:20161024224113p:plain

出来上がったファイルがこんな感じです。非常にシンプルですね。

ストーリーボードにボタンが一つあるだけ、ViewControllerもviewDidLoadで乱数生成器を初期化してボタンの押下に反応するメソッドだけのシンプルな構成です。

gist.github.com

f:id:euphonictechnologies:20161024224715p:plain

こんな感じでどうでしょう。

参考資料:

qiita.com

後々の作業のためにプロジェクトでなくてワークスペース上で作業するようにしましょう。空っぽのワークスペースを作ってそこにプロジェクトファイル(.xcodeprojファイル)をドラッグ・アンド・ドロップするだけです。完成。

ここからが本番です。

ライブラリをSwift Package Manager対応できるように切り出す

Swift Package Manager(SPM)というのが現在ベーシックなパッケージマネジャです。

SPMってなに?

Swift.org - Package Manager

非常にシンプルなパッケージマネジャです。Package.swiftというマニフェストファイルとパッケージを構成するスクリプトやライブラリバイナリ等からなります。

例えば今回作るライブラリは次のような構成になります。

~EFTest/RandomGenerator$ tree
.
├── LICENSE
├── Package.swift
├── README.md
├── RandomGenerator.xcodeproj
│   ├── Configs
...<xcodeprojの中身は省略>...
└── Sources
    └── RandomGeneratorXor128.swift

READMEとか本質的でないものを除くと構成要素は2つだけ、Package.swiftとSourcesディレクトリです。xcodeprojはiOSとXcode上で並行同時開発するためにリポジトリ上にあるだけで、本質的には必要ありません。

Package.swiftには2つの役割があります

つまり、その2つとは

  • あなたは誰?
  • 誰に依存してますか?

カウンセリングみたいになってしまいましたが、要はそのパッケージが何かということと、そのパッケージをビルドするのに他のどんなパッケージが必要なのか(どのバージョンを必要とするのか、どこにあってどうダウンロードするのか)の2つを指定する必要があります。

今回の場合、乱数生成器のライブラリは誰にも依存していないので、パッケージ名だけを指定してあるPackage.swiftを持っており、それを使うコマンドラインアプリケーションのPackage.swiftにはそのコマンドラインアプリケーションの名前と乱数生成器のライブラリのおいてあるgithubのURLとバージョンが収められています。

というわけで、切り出す先のプロジェクトを作ります

座学を終えて実践に移っていきましょう。まずはgithubに新しいリポジトリを作りましょう。使うパッケージはSPMにgithubからダウンロードさせるのが楽です。githubにリポジトリを作りましょう。適当に名前をつけてswift用の.gitignoreを含めておくと楽です。

READMEとか.gitignoreとかLICENSEだけの内容をgit cloneしてローカルのMac上に作業環境を作りましょう。で、そのディレクトリ上で

~/EFTest/RandomGenerator$ swift package init

してください。するとそのディレクトリの名前(つまりgithubのリポジトリ名)でパッケージを作ってくれます。マニフェストファイルであるPackage.swiftとか必要なディレクトリ構成とかテストのスクリプトとか一通り全て作ってくれます。

~/EFTest/RandomGenerator$ swift package init
Creating library package: RandomGenerator
Creating Package.swift
Creating .gitignore
Creating Sources/
Creating Sources/RandomGenerator.swift
Creating Tests/
Creating Tests/LinuxMain.swift
Creating Tests/RandomGenerator/
Creating Tests/RandomGenerator/RandomGeneratorTests.swift
~/EFTest/RandomGenerator$ tree
.
├── Package.swift
├── Sources
│   └── RandomGenerator.swift
└── Tests
    ├── RandomGenerator
    │   └── RandomGeneratorTests.swift
    └── LinuxMain.swift

3 directories, 4 files

こんな感じです。で、コマンドラインで作業してもよいのですが、つらいのでXcodeのプロジェクトを作りましょう。

~/EFTest/RandomGenerator$ swift package generate-xcodeproj
~/EFTest/RandomGenerator$ ls -l
total 8
drwxr-xr-x  7 Yoshinori  staff  238 Oct 24 23:12 RandomGenerator.xcodeproj
-rw-r--r--  1 Yoshinori  staff   88 Oct 24 23:08 Package.swift
drwxr-xr-x  3 Yoshinori  staff  102 Oct 24 23:08 Sources
drwxr-xr-x  4 Yoshinori  staff  136 Oct 24 23:08 Tests

ご覧の通り.xcodeprojが出来上がったので先程つくったiOSプロジェクトだけが置かれているワークスペースにこのプロジェクトを追加しましょう。

f:id:euphonictechnologies:20161024231549p:plain

RandomGeneratorがライブラリでEFTestがiOSアプリです。似たような感じになっていれば大丈夫です。TestsがRandomGeneratorにないのですが、私が消しました!気にしないでください!

そして切り出していきます

あとはこの2つのプロジェクトの間をファイルを行ったり来たりさせます。iOSアプリ側ではビルド設定の中でEmbed Frameworkに切り出す先のフレームワークを追加しておきましょう。

f:id:euphonictechnologies:20161024231918p:plain

ビルドエラーが出る度にクリーンするのが面倒なのでCopy only when installingをチェックしておくのがおすすめです。

で、ファイルが移動し終わったら多分やらなくちゃいけないのは

  • Swift 3にアップグレード
  • ビルド(多分クリーンが必要)

です。前者はコンパイラが指示してくれると思いますが、[Edit] -> [Convert] -> [To Current Swift Syntax...]でSwift 3準拠のコードに改めてください。

f:id:euphonictechnologies:20161024232147p:plain

次なんですが、ビルドがうまく行った方はそのままで大丈夫なのですがおそらくそのままビルドが通らないか実行時に怒られるかどっちかだと思います。何か変なことが起きたらクリーンビルドしましょう。

f:id:euphonictechnologies:20161024232343p:plain

ご存知コマンド+シフト+Kです。これで治らない場合はビルドディレクトリを削除しましょう。ここにオルトを加えると[Clean]が[Clean Build Folder ...]に変わると思いますのでそれを実行してください。それで治らない場合は…がんばってください。応援してます。

これで切り出したライブラリパッケージでiOSアプリが動くようになったと思います。動くようになったらライブラリに加えた変更をgithubにコミットしてプッシュしておきましょう。

サーバサイドのコマンドアプリケーションでライブラリパッケージを使う

Vagrantで使えるUbuntu 14.04をセットアップする

色々方法があると思いますが、何故か私がVagrantbox.esを使うと時間がすごくかかるのでいつもApache AuroraのVagrantファイルを改造して使っています。多分正攻法はこちら:

$ vagrant box add Ubuntu14.04 https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-i386-vagrant-disk1.box

私のやり方は:

$ wget https://raw.githubusercontent.com/apache/aurora/master/Vagrantfile
$ vi Vagrantfile
# 最後から3行目の"dev.vm.provision "shell", path: "examples/vagrant/provision-dev-cluster.sh""を削除
$ vagrant up

です。たぶん間違ったやり方なので皆さんは素直にUbuntuのbox作ってください。vagrant upしたら早速作ったばかりのUbuntuにログインしましょう。

UbuntuにSwiftがコンパイルできる環境を整える

基本的には公式のインストール方法を追いかければ大丈夫です。

swift.org

sudo apt-get update
sudo apt-get install -y clang libicu-dev

これで下準備は完了です。Swiftをインストールしていきましょう。

wget https://swift.org/builds/swift-3.0-release/ubuntu1404/swift-3.0-RELEASE/swift-3.0-RELEASE-ubuntu14.04.tar.gz
tar xzf swift-3.0-RELEASE-ubuntu14.04.tar.gz

解凍した一式の中にあるswiftにパスを通します。私はきれい好きなズボラなのでホームディレクトリの下にswiftというディレクトリを作ってその中にアーカイブを解凍しました。~/swift/usrがそのアーカイブ内のusrへのリンクになるようにしておいてあとでバージョン切り替えを簡単にする魂胆です。

$ mkdir swift
$ mv swift-3.0-RELEASE-ubuntu14.04.tar.gz swift
$ cd swift/
$ tar xzf swift-3.0-RELEASE-ubuntu14.04.tar.gz
$ ls
swift-3.0-RELEASE-ubuntu14.04  swift-3.0-RELEASE-ubuntu14.04.tar.gz
$ ln -snf /home/vagrant/swift/swift-3.0-RELEASE-ubuntu14.04/usr usr
$ ls -l
total 114632
drwxrwxr-x 3 vagrant vagrant      4096 Oct 30 07:52 swift-3.0-RELEASE-ubuntu14.04
-rw-rw-r-- 1 vagrant vagrant 117377293 Sep 13 23:25 swift-3.0-RELEASE-ubuntu14.04.tar.gz
lrwxrwxrwx 1 vagrant vagrant        53 Oct 30 07:53 usr -> /home/vagrant/swift/swift-3.0-RELEASE-ubuntu14.04/usr

こんな感じ。お好みで。 本当はダウンロードしたファイルをmd5で検証すべきなのだけど面倒なので省略。いつか痛い目見るで。

あとはパスを通しましょう。

$ vi .profile
$ source .profile
$ printenv PATH
/home/vagrant/swift/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games

こんな感じです。

最後にswiftの動作確認をしましょう。

$ swift --version
Swift version 3.0 (swift-3.0-RELEASE)
Target: x86_64-unknown-linux-gnu
$ swift
Welcome to Swift version 3.0 (swift-3.0-RELEASE). Type :help for assistance.
  1> 1 + 2
$R0: Int = 3
  2> let a = 1 + 3
a: Int = 4
  3> let b = 4 + 9
b: Int = 13
  4> let c = a + b
c: Int = 17
  5> ^D

いい感じですね!これでswiftがインストールできました。

数あてゲームをビルドする

それでは最後の工程、数あてゲームをさっきの乱数ライブラリを用いてビルドしてみましょう。

$ mkdir EFTest_CommandLineApp
$ cd EFTest_CommandLineApp/
$ swift package init --type executable

これでプロジェクトの雛形ができました。

vagrant@aurora:~/codes/EFTest_CommandLineApp$ tree
.
|-- Package.swift
|-- Sources
|   `-- main.swift
`-- Tests

2 directories, 2 files

こんな感じです。やらなければいけないことは

  • Package.swiftを修正してこのプロジェクトの設定と使うライブラリの設定を与える
  • swift package updateをして使うライブラリをgithubからダウンロード、インストールする
  • main.swiftを書く
  • ビルドして実行

です。

Package.swiftを修正してこのプロジェクトの設定と使うライブラリの設定を与える

早速パッケージマニフェストファイルPackage.swiftを修正しましょう。

gist.github.com

dependenciesには前のセクションであなたが作ったライブラリのgithubのurlを指定してください。 ここでmajorVersion: 1, minor: 2という部分が不思議だと思うのですが、そうなんです、Swift Package Managerはgit上のタグ付けされた内容しか使えないのです。バージョニングせよということなんでしょう。なのでGithub上でタグ付け、つまりリリースをしましょう。

f:id:euphonictechnologies:20161030171645p:plain

Githubのリリースの項目から

f:id:euphonictechnologies:20161030171656p:plain

新しいドラフトを編集する画面に移り

f:id:euphonictechnologies:20161030171807p:plain

という感じでバージョンをつけます。画面では1.3.0になっていますが、普通は1.0.0ですね。もしくはα版扱いにして0.1.0としてもよいと思います。お好きにどうぞ。で、ここでつけた最初と2番めの数字がそれぞれmajor/minorバージョンとしてPackage.swiftに指定する番号になります。

dependencies: [
        .Package(url: "https://github.com/ysnrkdm/EFTest_RandomGenerator.git", majorVersion: 1, minor: 2)
    ]

ここですね。

swift package updateをして使うライブラリをgithubからダウンロード、インストールする

Package.swiftの設定が終わったらライブラリをダウンロードしましょう。

swift package update

です。そうすると…

Cloning https://github.com/ysnrkdm/EFTest_RandomGenerator.git
HEAD is now at e8da832 Commit xcodeproj
Resolved version: 1.2.0
vagrant@aurora:~/codes/EFTest_CommandLineApp$ tree
.
|-- Packages
|   `-- RandomGenerator-1.2.0
|       |-- LICENSE
|       |-- Package.swift
|       |-- RandomGenerator.xcodeproj
|       |   |-- Configs
|       |   |   `-- Project.xcconfig
|       |   |-- project.pbxproj
|       |   |-- RandomGenerator_Info.plist
|       |   `-- xcshareddata
|       |       `-- xcschemes
|       |           |-- RandomGenerator.xcscheme
|       |           `-- xcschememanagement.plist
|       |-- README.md
|       `-- Sources
|           `-- RandomGeneratorXor128.swift
|-- Package.swift
|-- Sources
|   `-- main.swift
`-- Tests

9 directories, 11 files

という感じでさっきプッシュしたファイルがズラッとダウンロードされているのがわかります。もしライブラリに変更を加えたらそれを再びリリースして、Package.swiftのバージョンを修正してswift package updateすると変更がきちんと反映されます。

main.swiftを書く

今回は簡単にこんな感じにしてみました。

gist.github.com

乱数を4回空打ちしているのが最高にアホですね。すみません。

ビルドして実行

$ TOOLCHAINS=swift swift build
Compile Swift Module 'RandomGenerator' (1 sources)
Compile Swift Module 'EFTest_CommandLineApp' (1 sources)
Linking ./.build/debug/EFTest_CommandLineApp

これでビルドできました。遊びましょう!

ビルドされたファイルはLinking ...が言うとおりプロジェクトルート下の.buildの中にあります。./.build/debug/EFTest_CommandLineAppですね。

$ .build/debug/EFTest_CommandLineApp
Seed set: -3618550839602295192
Your guess? [0-99, >=100 to exit]
50
Less!
Your guess? [0-99, >=100 to exit]
25
More!
Your guess? [0-99, >=100 to exit]
32
More!
Your guess? [0-99, >=100 to exit]
37
Less!
Your guess? [0-99, >=100 to exit]
34
Bingo!
Generating next number...
Your guess? [0-99, >=100 to exit]
100
Bye!

楽しかったですね。

まとめ

iOSとLinuxのコマンドラインアプリケーションとの間でコードを共有するのは簡単。

今回紹介したコードはすべて下のGithubから利用可能です。自分でライブラリ作ったりコード書くの面倒だけどswift on linux使ってみたい方や私のアレな説明でわからなかった方は是非どうぞ。

github.com

github.com