サーバサイドSwiftって全然盛り上がってないですね
なんかフロントエンド盛り上がってますがサーバサイドっていまいちそういう感じしませんね。悲しいです。フロントエンドアプリばっかり作ってるとフロントエンド脳になってまうで。
というわけで拙作のオセロアプリは着々と進捗があるのですが、ここ1年ほどアップデートをしておりません。iOS側のオセロアプリとサーバーサイドアプリケーションで同じオセロAIエンジンを共有したい、という文脈でどうしてもサーバサイドとフロントエンドでライブラリを共有したいのです。
iOSアプリとLinuxで動くコマンドアプリケーションでコードをシェアする方法…正攻法で挑んでみる
なんとなくググってみたのですが、いまいち決定打みたいなのはまだない・・・ように感じます。おそらくSwiftをヘビーに使っている会社の社内的なノウハウみたいなものはもうあるんでしょうが、あまり公開情報が引っかかりませんでした。なので正攻法で挑んでみようと思いました。
今回のゴールの図
では、iOSのSingleViewApplicationとUbuntuのコマンドラインアプリケーションでライブラリを共有します。こんなアプリです。
…アプリってほどのものか?あくまでexampleですから。
ボタンを押すと次々乱数を表示します。それだけです。便利ですね!
んで、その擬似乱数の生成器が共有部分になっていて、コマンドラインアプリケーション側はこんな感じで使います。
昔懐かし数あてゲームです。これをiOSで作るのは面倒だったのでiOS側はボタンを押して乱数を表示するだけのアプリになりまして、すみませんすみません。
というわけで、例にしては意外に応用しがいのあるユースケースじゃないかなーと。
今回の流れ
結構ありがちな流れとして(たまたま自分がそうだっていうだけですが)
- iOSでアプリをもりもり作ってきて、手元に動くアプリがある
- その一部をライブラリとして切り出したい
- でもってそのライブラリをサーバサイドで使えるようにしたい
という状況を想定しております。なので今回の説明も
- まずは完動品のiOSアプリを作ります
- そしてライブラリを切り出します
- それをサーバサイドのコマンドラインアプリで使います
という流れで行こうと思います。
まずはiOSアプリ側で乱数表示アプリを完成させる
というわけでMac上で普通にXcodeを使ってSingleViewApplicationを作ります。
出来上がったファイルがこんな感じです。非常にシンプルですね。
ストーリーボードにボタンが一つあるだけ、ViewControllerもviewDidLoadで乱数生成器を初期化してボタンの押下に反応するメソッドだけのシンプルな構成です。
こんな感じでどうでしょう。
参考資料:
後々の作業のためにプロジェクトでなくてワークスペース上で作業するようにしましょう。空っぽのワークスペースを作ってそこにプロジェクトファイル(.xcodeprojファイル)をドラッグ・アンド・ドロップするだけです。完成。
ここからが本番です。
ライブラリをSwift Package Manager対応できるように切り出す
Swift Package Manager(SPM)というのが現在ベーシックなパッケージマネジャです。
SPMってなに?
非常にシンプルなパッケージマネジャです。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プロジェクトだけが置かれているワークスペースにこのプロジェクトを追加しましょう。
RandomGeneratorがライブラリでEFTestがiOSアプリです。似たような感じになっていれば大丈夫です。TestsがRandomGeneratorにないのですが、私が消しました!気にしないでください!
そして切り出していきます
あとはこの2つのプロジェクトの間をファイルを行ったり来たりさせます。iOSアプリ側ではビルド設定の中でEmbed Frameworkに切り出す先のフレームワークを追加しておきましょう。
ビルドエラーが出る度にクリーンするのが面倒なのでCopy only when installingをチェックしておくのがおすすめです。
で、ファイルが移動し終わったら多分やらなくちゃいけないのは
- Swift 3にアップグレード
- ビルド(多分クリーンが必要)
です。前者はコンパイラが指示してくれると思いますが、[Edit] -> [Convert] -> [To Current Swift Syntax...]でSwift 3準拠のコードに改めてください。
次なんですが、ビルドがうまく行った方はそのままで大丈夫なのですがおそらくそのままビルドが通らないか実行時に怒られるかどっちかだと思います。何か変なことが起きたらクリーンビルドしましょう。
ご存知コマンド+シフト+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がコンパイルできる環境を整える
基本的には公式のインストール方法を追いかければ大丈夫です。
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を修正しましょう。
dependenciesには前のセクションであなたが作ったライブラリのgithubのurlを指定してください。
ここでmajorVersion: 1, minor: 2
という部分が不思議だと思うのですが、そうなんです、Swift Package Managerはgit上のタグ付けされた内容しか使えないのです。バージョニングせよということなんでしょう。なのでGithub上でタグ付け、つまりリリースをしましょう。
Githubのリリースの項目から
新しいドラフトを編集する画面に移り
という感じでバージョンをつけます。画面では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を書く
今回は簡単にこんな感じにしてみました。
乱数を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使ってみたい方や私のアレな説明でわからなかった方は是非どうぞ。