Tryswift jeff hui cover

Swiftでライブラリを作ろう

ライブラリはコードを共有するこれからの一歩となりますが、そんなに簡単な作業ではありません。プラットフォーム、パッケージマネージャー、テストのすべてがライブラリとして成功する決定的な要素となります!try! Swiftのこの発表から、Swiftでライブラリを公開・メンテナンスする場合に必要なすべてのツールや過程を学びましょう。


すばらしいコードで書かれているライブラリ (0:00)

すばらしいコードを持っていると想像してみてください。それはリアクティブプログラミング、プロミス、リザルト型のような新しい書き方、もしくは既存APIのラッパーかもしれません。もっとSwiftらしくしたいと思ってしまうようなC言語ベースのAPIを持っているAddress Bookやキーチェーンが良い対象になるかもしれません。

このようなすばらしいコードを書いたとしたら、世界中で共有したいと思うでしょう。それはあなたの同僚や他の人にもプラスになるでしょう。彼らの数時間・数日分の労力を減らせます。さて、どこから始めましょう?ライブラリを作るにあたってベストな方法は何でしょうか?はい、答えは1つしかありません。それはライブラリを作ることです。

新規プロジェクトを作成してコードを書いたとします。しかし、それはスタート地点に立っただけです。なぜなら、コードをどこからでもアクセス可能にするのが目的だからです。そのコードをtvOS、watchOS、iOS、OS Xなど複数のプラットフォーム上で実行したいと思うでしょう。もちろんオープンソースとなったSwiftならLinux上でも実行できます。

デベロッパーのためにライブラリとの統合を簡単にする必要があります。また導入方法もCocoaPods、Carthage、Swift Package Manager、Xcodeのサブプロジェクトなど、彼らの好みの方法を用意して簡単に導入できるようにする必要があります。

次に、デベロッバーからコントリビューションを受け取るようになると、ある程度の質を保ちたいと思うでしょう。一般的には、テストを頻繁に実行して不具合の発生や互換性の無い変更を減らすようにします。

最後に、リリース管理をする必要があります。対応しているすべてのパッケージマネージャーやプラットフォームに対して、デベロッパーがアップグレードする最適なタイミングに一貫した方法で公開する必要があります。

実際にこれらはかなりの作業量となります。これらはコード周りの作業となり、ほとんどが本来ライブラリとして配布しようといたものではありません。それは、できるだけ多くのユーザーに良い体験をしてもらうための環境整備に過ぎません。この発表の本題はここにあります。これはコード自体がすばらしいかどうかの問題ではありません。なぜならそれらのコードは既にすばらしい状態だからです。そのコードに対しては何も手を加える必要はありません。簡単に使えたり、満足して使えるライブラリのコードを取り巻く環境についてが本題です。

Swiftはすばらしいプログラミング言語ですが、エコシステムとして多くのサードパーティーのライブラリがあります。以前より多様なやり方で目的を実現できるようになりました。それは過去に行われてきたことを糧にして構築されているからです。

ライブラリの実例 (2:32)

それではSwiftでライブラリを構築してみましょう。

これから使う具体的なデモやスニペットは内容を非常に分かり易くしてくれると思うので、例を見ながら進めましょう。すべての良いライブラリがそうであるように、まずは良い名前が必要です。そこで、このライブラリ名をSnorlax(カビゴン)とすることにします!

記事の更新情報を受け取る

このライブラリは1つの機能しか持っていません。ランダムな時間スリープするという機能です。したがって、メソッドも1つしかありません。ドキュメントもあり配布できる準備が整っているので、本番環境への準備ができています。

良いライブラリに従って、テストコードを書くことにします。今回の場合は、非同期テストを用意します。テストを書く方法や役割はたくさんありますが、今回のライブラリには非同期テストのみを行うことにします。テスト自体が何をするかは問題ではなく、重要なことは他のプラットフォームをサポートする度にテストを変えていくことです。

ここにSnorlaxのすべてのコードがあります。間違いなくあなたが書く方が良いコードを書けるでしょう。

ビルド設定 (4:29)

それではライブラリ化しましょう。まずはXcodeの新規iOSフレームワークにコードを入れます。Xcodeが提供する最小限の状態からデベロッパーがスムーズに統合できるように、いくつかのビルトインされた設定を変えて、これから見せる連続するステップをXcodeで作るフレームワークに反映させます。今回はiOSのみ取り扱います。

最初に、スキームを共有できるようにします。これでコマンドラインで行う継続的インテグレーションのためのテストを実行できます。それぞれのビルド設定に対して実行されるテストが、ライブラリを作っているすべての人達に同期されることが保障されます。

次に、Snorlaxは拡張性に優れています。ビルド設定のGeneralタブの下部にAllow app extensions API onlyというチェックボックスがあります。これはライブラリがウィジェットやWatchアプリなどのエクステンションと統合できるかどうかの設定です。これをチェックすると、すべての準備が整います。

最後に、ビルド設定のProduct Module Nameをリネームするか、モジュール名としてそのまま使うか決めます。すべてのフレームワークで同じ名前が望ましいです。そうするとデベロッパーがライブラリを使う時にインポートするモジュール名がライブラリ名となるので統一感があります。デフォルトの設定だと、プラットフォームごとにOS名がポストフィックスとして付くかもしれないので、デベロッパーがインポートしやすいように統一したいと思うでしょう。

これで完了です!やりましたね、iOSフレームワークが完成しました!これをすべてのプラットフォームに拡張できますが、時間を節約するために自分自身で同じ作業を繰り返さずに、同じことを他のプラットフォームでもやりましょう。

watchOSはテストバンドルを持てないので、ソースファイルを忘れれないように確認して少し気をつけながら、適切な方法で統合しましょう。

継続的インテグレーションでのテスト (6:43)

次はテスト、より細かく言うと継続的インテグレーションについてです。継続的インテグレーションの設定をライブラリとすることで、ライブラリに変更を加えて何かが壊れた時にすばらしいフィードバックがもらえます。オープンソースにするのは、主にライブラリを共有したりコントリビューションをもらうためです。

選択は1つしかなく、Travis CIを使います。なぜなら、他のCIサービスが提供していない複数の機能を持っているため便利だからです。Jenkinsも試せますが、作業量が多くなります。Xcode betaを対象にしたい時や、複数のOSでビルドしたい時や、バージョンが異なるSDKでビルドしたい時などJenkinsは他の多くの機能を持っています。すべて面倒を見てくれるので、サーバーを走らせる必要もなく便利です。

Travis CIの場合、travis.ymlファイル1つで十分です。Appleのプラットフォームに関しては、本当にこれだけが必要なものです。どうやって実行するかを3行で記述する必要があります。Xcode 7.2上で走らせるとします。Objective-Cで書いてあるとします。冗談です、Swiftで書かれています。しかしTravis CIはすべて正しく設定してくれます。そして、スクリプトでテストを走るようにします。私はローカルでテストを走らせる場合を考えて、スクリプトで実行することを好みます。Travis CIに走らせるコマンドをコピーアンドペーストする代わりに、テストスクリプトで簡単にテストを走らせられます。

このテストスクリプトはそれほど難しくありません。そう見えるかもしれませんが、主に複数プロットフォーム向けにXcodeのテストを走らせているだけです。watchOSはテストを走らせられないので、対象となるのはOS X、iOSとtvOSです。早期に失敗するシェルスクリプトコマンドも用意します。それほど複雑ではありません。これだけでTravis CIのインテグレーションができました。Travis CIのインテグレーションが設定してあるプロジェクトに誰かがPRを送る時に、自動でテストを実行してくれます。PRをスマートフォンからマージしたい時に他のマシンでテストを実行してくれるのでかなり便利です。

パッケージマネージャー (9:11)

ここまでで、デベロッパーが上記のインテグレーションを手動で済ませていればSnorlaxは使える状態になっています。もしSnorlaxをサブプロジェクトとして統合する方法を知っていたら、CocoaPodsCarthageSwift Package Managerの3つの有名なパッケージマネージャーにも対応すべきでしょう。

CocoaPods (9:21)

最初に最も有名なCocoaPodsを見てみましょう。CocoaPodsは最も簡単に使えます。Xcodeのプロジェクトがどうやってビルドするかよく知らなくても、CocoaPodsがサードパーティーとの依存関係の面倒を見てくれます。CocoaPods用にPodspecファイルを定義すると、ライブラリの管理者が適切な方法でセットアップしてくれるのと同じ設定をしてくれます。

使い始めるには、1つのコマンドを叩く必要があります。そうすると、CocoaPodsが自動的にファイルを生成したり、設定ファイルの微調整をしたり、いくつかの初期設定などしてくれます。しかし、もっと重要なことは、依存関係や必要なビルド設定をリストしてくれることです。

今回はFoundationの依存関係をリストに追加する必要がありますが、PodspecファイルでFoundationを追加するだけです。もし他のサードパーティーの依存がある場合は、Podfileに追加する必要があります。Snorlaxはその必要がないので先に進みます。

Carthage (10:31)

CarthageはXcodeがプロジェクトをビルドする方法とより親和性があります。使用するユーザーの観点から見ると、ファイルを実際にプロジェクトと連携させる必要があります。プロジェクト内でどのバージョンが必要かを判断して依存関係の構築を対処してくれますが、最終的なアプリケーションとの連携はデベロッパー自身で行う必要があります。手動でプロジェクトに依存しているライブラリを追加してきた人にとってバージョンの選択を自動でやってくれるので、これはかなり良い代価方法です。それ以外にCarthageができることはほとんどありません。コマンドを走らせてCarthageが実際にプロジェクトをビルドできるか確認できるので、試してみましょう。

carthage build --no-skip-current

no-skip-currentフラグを付けると、以前にビルドした内容を利用せずに必ず毎回ビルドしてくれます。もし依存がある場合は、CarthageのCartファイルで指定する必要があります。ただし、Snorlaxは特に何も別のライブラリを使用しないので、何もする必要がありません。

Swift Package Manager (11:37)

Swift Package Managerは他のパッケージマネージャーより実際に作業量が多く、比較的新しいのでまだ発展途上の状態です。事実として、昨日更新されたばかりで、常に変化しています。Swift Package Managerで面白いことは、Linuxをサポートしている唯一のツールだということです。Swift Package Managerを使用して、Linuxのサポートもしてみましょう。

Swift Package Managerの仕組みは、Package.swiftファイルを持っていてCocoaPodsのようにターゲットごとに定義できます。デフォルトでは、今回だとSnorlaxのように1つのターゲットを示していて、それだけで十分です。新しいSwiftのスナップショットでは、ビルドしたいターゲットとテストターゲットを推測してくれます。今回のプロジェクト構成でどう動くか見てみましょう。依存関係があればCocoaPodsのようにリストする必要がありますが、Snorlaxに関しては何もしません。

Snorlaxは非常に簡素なプロジェクトです。2つのファイルがあって、Swift Package Managerはプロジェクトをどうやってビルドするか推測するために特定のフォルダ階層を必要とします。Snorlaxフォルダ以下にPackage.swiftファイルを置いて、ターゲット名となっているSnorlaxフォルダ以下にソースファイルを移動します。ここにすべてのコードが入っています。

テストも同様の処理をします。テストファイルが1つあり、テストターゲット名のフォルダ以下に移動します。Snorlaxでは、Package.swiftから名前がきています。最後に、Linuxのためにテストの実行方法をSwift Package Managerに伝えるファイルを追加します。Objective-Cのランタイムを持っていないため、どこにテストがあるか確認する必要があります。

mainファイルを見てみましょう。

// LinuxMain.swift
import XCTest
@testable import SnorlaxTest

// ここがLinux上でのテストのエントリーポイント

XCTMain([
    SnorlaxTests(),
])

内容は非常に単純です。XCTestをインポートして、XCTMainを呼んでいるだけです。それと、テストターゲットにあるXCTestCaseの全クラスをリストしています。今回の場合は1つしかないのでそれしか実行されません。非常に分かり易くなっています。LinuxMainはXcodeプロジェクトのどこかにあるだけではダメで、プロジェクト内にないと意味がありません。Xcodeはこのファイルについて何も知る必要がありませんが、プロジェクト内にあるかしっかり確認してください。

次に、テストの内容を更新する必要があります。まず、XCTestはLinuxでの非同期処理をサポートしていないので、それを取り除いて同期処理に置き換える必要があります。次に、Objective-Cのランタイムを使用できないためにLinux用のXCTestはリフレクションをサポートしていないので、コードを追加する必要があります。手作業でXCTestCaseProviderを継承しているすべてのテストケースをリストしなければなりません。しかし、XCTestCaseProviderは実際にLinux用のSwiftでしか利用可能でないために、ここで問題があります。AppleのプラットフォームごとにXCTestCaseProviderの設定を定義して、コンパイルエラーが起きないようにします。このXCTestCaseProviderの設定をどこかで定義さえしておけば、他のすべてのテストコードは問題なく実行されるでしょう。

ここまでで、コードをビルドできるようになります。ライブラリとテストバンドルのビルドを実行するSwift Buildとテストを実行するSwift Testを実行できようになりました。

いや、少し待ってください。まだいくつか問題が残っています。Swift Linuxはバージョンが常に変わって毎週新しいバージョンがリリースされているので、まだ安定していません。最新の状態に保つのは追加作業が必要なので、この作業を最適化できればすばらしいことだと思います。なぜなら、ライブラリは開発しているデベロッパー間や継続的インテグレーション上でバージョンを同期する必要があります。また、常に最新の状態を保ちながら他のコントリビューターにどのバージョンを使用するか伝えて連携をとるのは、非常に面倒なことです。

その代わりに、Kyle Fullerが作ったswiftenvというライブラリを使うのをお勧めします。RubyやPythonのRVMやPyenvを使った経験があるなら、それらのSwift用だと思ってください。プロジェクトに対して特���のSwiftのバージョンを使用できます。swiftenvを使えば、最新のスナップショットをインストールしながら、ローカルでは使用している最中のバージョンをそのまま使えます。

swiftenv install DEVELOPMENT-SNAPSHOT-2016-03-01-a
swiftenv local DEVELOPMENT-SNAPSHOT-2016-03-01-a

これで、通常のSwiftコマンドを実行すると、自動的に適したバージョンのSwiftを使えます。

swift build
swift test

CIも徐々に変わっています。先ほど説明したbuild matrixをこれから使う必要があります。このライブラリはOS XとLinuxを対象とするので、Xcodeでテストビルドを実行するかSwift Package Managerを使うかのどちらかになります。1行に短縮されたコマンドswiftenvのインストールを行います。詳しくは後ほど説明します。

環境変数を使ってテストを実行します。それでSwift Build Testと以前にリストしたXCTextが実行されます。Travis CIでも同じコミット状態のビルドが3種類あり、どのプラットフォームでテストが失敗してるか確認できます。もし誰かが変更を加えると、Linuxで失敗したか、OS Xで失敗したか、OS XでSwift Package Managerが失敗したかなど分かります。これは最高にクールなことです。

バージョニング (17:42)

もうリリースするほとんどの準備ができました。このすばらしい新しいライブラリをリリースする寸前まで来ました。設定がすべて済んでいて、あとはバージョンを付けてリリースするだけです。しかし、どのバージョンを使うのでしょうか?どうやってバージョン番号をセットするのでしょうか?バージョン番号にはどんな意味があるのでしょうか?

実際に多くのライブラリにとってバージョンは非常に一般的なことです。セマンティックバージョニングを使うために記号化されています。一番右端の最も重要でない番号はバグ修正のみを指し示すパッチバージョンです。パッチバージョンが上がれば、バグ修正のみライブラリに含まれて、互換性の無い変更は無く、新しい機能の追加されていないことになります。

マイナーバージョンをあげると、互換性の無い変更は無いが、新しい機能が追加されてバグ修正も含まれていることになります。

一方で、メジャーバージョンはすべてを変更します。すべて変更されるかもしれませんし、すべて壊れているかもしれません。すべての新しい機能が含まれているかもしれないし、うまくいけばバグが修正されているかもしれません。

自分で変更した内容と一致させるために、特定のバージョンを選択する必要があります。今回は最初のバージョンとなるので、好きなバージョン番号をセットして構いません。これでリリースプロセスを進められます。さらにセマンティックバージョンについて情報が欲しければ、このページを確認してください。

リリースフロー (19:21)

これで、ライブラリのリリースフローのルーティーンができました。そのほとんどは簡単です。なぜリリースフローが必要かをいくつか説明します。

そのほとんどの理由はCocoaPodsをサポートするためで、それが最初のステップです。ドキュメントやPodspecファイルで指定するバージョン番号を決める必要があります。なぜなら、Podspecファイルにはバージョン番号を含める必要があり、それを新しいバージョン番号としてセットするからです。

それをコミットして、GitHubもしくはソース管理ツールにプッシュしましょう。新しいバージョン番号でタグを付けると、CarthageやSwift Package Managerがどのバージョン番号を取得すべきか判断してくれるので便利です。そのことは覚えておきましょう。

最後にプッシュしたかを確認しましょう。そうすると、それらのパッケージマネージャーからリポジトリを見れるようになります。その後にPodspecをCocoaPodsのtrunkにプッシュする必要があります。これで新しいライブラリのバージョンがリリースされたことをCocoaPodsに伝えられます。そしてCocoaPodユーザーが新しくリリースされた新しいバージョンを使えるようになります。

さらに、GitHub上で目立つような分かり易いリリース情報を作成できます。そして、「ランダムな時間スリープできる新しいライブラリができました!」と世界中に発信できます。

ライブラリのすべてをGitHub上のSnorlaxで公開しています。それは最もつまらないコードですが、CocoaPodsやSwift Package Managerの更新などのライブラリ設定関連のすべての基盤整備ができています。コミット履歴を見てみるのがお勧めです。このコミット履歴はそれぞれのステップに分類されています。今回の内容だと複数のプラットフォームにサポートしたことです。

また補足としてサンプルも用意しています。 SnorlaxSamplesはSnorlaxの依存関係を統合するサンプルです。これで、どうやってCarthage、CocoaPods、Swift Package Manager上でどう動くか見られます。以上になります、ありがとうございます!

Q&A (21:30)

Q: バージョニングについて、vをバージョンタグの先頭に付けるべきですか?

Jeff: これはタブやスペースと同じような問題でしょう。私はいつもvをつけます。タグバージョンごとにフィルターするのが楽だからです。もしSwift用に違うブランチを対象としていて、古いSwiftバージョンを使用していたり古いリリースや開発用に別の用途で使用している場合に、タグをカテゴリごとにまとめる方法があったほうが良いと思います。なので私はvを付ける方を好みます。もちろん、ライブラリがそういった使い方を必要としない場合はVは必要無いでしょう。

Q: もしライブラリがFoundation以外に他のライブラリに依存している場合、何か具体的にした方が良いことはありますか?

Jeff: Snorlaxのリポジトリでは、他の依存関係においてライブラリの一部として取り込む例を含めています。追加でやる作業がありますが、Swift Package Manageだと少し複雑になります。多くのライブラリがSwift Package Managerをまだサポートしていないのが理由です。したがって、CarthageやCocoaPodsを使うのが好ましいです。

CocoaPodsを使ってあなたのライブラリを統合するのはお勧めできませんが。なぜなら、CocoaPods経由でライブラリを使うすべてのユーザーにその依存関係を求めることになるからです。もしサードパーティーのライブラリを統合する場合は、Carthageかgitサブモジュールを使って手作業で依存管理するべきでしょう。

Q: 社内用のプライベートライブラリはどう作ればいいですか?

Jeff: それは使用している現場のエコシステムに完全に依存します。CocoaPodsを使用している場合は、プライベートなSpecリポジトリを作成してそこで依存関係を管理してください。ただし、GitHubエンタープライズを内部で利用している場合は、Carthageかgitサブモジュールを代わりに使用してください。

すべてのサンプルを使ってみる必要はありません。これはすべてのプラットフォーム上で起こりうる可能性すべてを完璧にカバーしているものです。すべてのライブラリがすべてのプラットフォームでSwiftをサポートする必要はありません。なぜなら、Objective-Cのラッパーになるかもしれないからです。その場合は、Linuxをサポートする必要がありません。ライブラリが置かれている環境によって、適した部分だけ抜き取って使用してください。

About the content

2017年3月のtry! Swift Tokyoの講演です。映像はRealmによって撮影・録音され、主催者の許可を得て公開しています。

Jeff Hui

Jeff Hui is an full-stack engineer specializing in iOS development. He’s worked on a number of iOS apps as a consultant. He’s an active open source contributor and the core team member to Quick & Nimble testing frameworks.

4 design patterns for a RESTless mobile integration »

close