本投稿では Swift のライブラリ管理に欠かせない Carthage について、
- Carthage の基本的な使い方
- iOS 7 での Carthage の使い方
を説明します。
はじめに
ライブラリの依存関係を解決し管理するのは大変な作業です。 Swift にはライブラリの依存管理ツールとして主に Carthage と CocoaPods という選択肢があります。
Carthage はとてもシンプルな仕組みで動いているので、余計なファイルが作られたり変な制約が生まれたりしません。できるだけシンプルな状態でリポジトリを保ちたい人に向いています。 Carthage と CocoaPods の比較はこちらの投稿(または、その翻訳元の英文である Carthage の README )が詳しいです。
もし今 Git のサブモジュールを使ってライブラリを管理しているのであれば、 Carthage を使うことで幸せになれると思います。サブモジュールをでライブラリを扱うシンプルさ(何が起こっているのか自分ですべて把握し管理できるという意味で)と、コマンド一つでライブラリの依存関係を解決して checkout できる便利さを両立できます。
しかし、、 Carthage は iOS 8 以降でのみサポートされている Embedded Binaries を前提としており、README にも
Note that Carthage only supports dynamic frameworks, which are only available on iOS 8 or later
と書かれています。現実的にはまだ iOS 7 を切り捨てづらいことから、 Carthage は使ってみたいけどあきらめているという人も多いかもしれません。しかし、これは iOS 8 でないと Carthage を使えないことを意味しません。 実は iOS 7 でも Carthage を十分に活用することができます。
本投稿ではまず Carthage の使い方について説明し、その後、 iOS 7 で Carthage を使う方法について説明します。
Carthage の基本的な使い方
インストール
Carthage は Homebrew を使って簡単にインストールできます。
Homebrew とは Mac でデファクトになっている(と思う)パッケージマネージャで、ターミナルで↓のコマンドを実行するだけでインストールできます( MacPorts を使っていると競合するので気をつけて下さい)。
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
Homebrew を使えば、次のコマンドで Carthage をインストールできます。
brew install carthage
もし、後日 Carthage のアップデートを反映したければ次のコマンドを実行するだけです。
brew upgrade carthage
Cartfile
Carthage では、 Cartfile と呼ばれるテキストファイルにインストールしたいライブラリを記述します。
例えば、 Alamofire の 1.3 以降の最新リリースと、 Swifty JSON の 2.0 以降の最新リリースをインストールするには次のように書きます。とても簡単ですね。
github "Alamofire/Alamofire" >= 1.3
github "SwiftyJSON/SwiftyJSON" >= 2.0
Cartfile
を書いたら次のコマンドを実行すると、ライブラリが checkout されてビルドされます。
carthage update
コマンドが完了したら Carthage
というディレクトリができているので見てみましょう。 Carthage
ディレクトリの中身は次の通りです。
-
Carthage/Checkouts
: リポジトリを clone した結果 -
Carthage/Build
: ビルドされた Framework
通常は Carthage/Build
の中の Framework を Embed して使います( iOS 8 以降で標準の Framework の使い方です)。 iOS 7 については後述します。
GitHub 以外のリポジトリの指定方法
上記の Cartfile
では GitHub のリポジトリを指定していまが、 GitHub 以外の Git リポジトリを指定することもできます。 github
の代わりに git "https://foo.orf/bar.git"
にように書けば、任意の Git リポジトリを対象とすることができます。
バージョンやブランチ、特定のコミットの指定方法
Cartfile
の >= 1.3
の部分はバージョンを指定しているのですが、 Carthage の Documentation にある通り、その他にも様々な指定が可能です。
= 1.0 for “at least version 1.0”
- ~> 1.0 for “compatible with version 1.0”
- == 1.0 for “exactly version 1.0”
- "some-branch-or-tag-or-commit" for a specific Git object (anything allowed by git rev-parse)
>=
を使えば、指定したバージョン以降の最新のバージョンが、 ~>
を使えば指定したバージョンと 互換性がある 最新のバージョンが、 ==
を使えば指定した特定のバージョンがインストールされます。
それ以外に、 4 で書かれているように特定のブランチやコミットを指定することもできます(下記は PromiseK の experiment
ブランチを指定する例です)。
github "koher/PromiseK" "experiment"
ブランチの代わりにコミット番号を書けば特定のコミットを指定することができます。
Semantic Versioning
~>
を使えば指定したバージョンと互換性がある最新のバージョンがインストールされると書きましたが、互換性はどのように検証されているのでしょうか。実は、バージョンの付け方に秘密があります。
Carthage 対応のライブラリは Semantic Versioning というバージョンの付け方をする必要があります。 Semantic Versioning では、 1.2.3
というバージョンがあるとすると、 1
の部分が MAJOR version 、 2
の部分が MINOR version 、 3
の部分が PATCH version と呼ばれます。
互換性のない変更が行われたときには MAJOR version を、 API が追加されるだけなど互換性がある変更が行われた場合には MINOR version を、(互換性のある)バグフィックスなどの場合は PATCH version を更新するというルールでバージョンが付けられるので、 Semantic Versioning に従っているという前提であれば、バージョンを調べるだけで互換性の有無がわかるのです。
コミットとバージョンの関連付け
どのコミットがどのバージョンであるかは Git のタグで管理されます。 例えば、 1.2.3
のようにタグを付けておくだけで、 Carthage はそのコミットが 1.2.3
というバージョンなんだと認識してくれます。
Cartfile.resolved
carthage update
を実行すると、 Carthage
ディレクトリとは別に Cartfile.resolved というファイルが生成されます。
このファイルには、 Cartfile
に基いて 実際にチェックアウトされたバージョン(タグ)/ブランチ/コミット が記載されています。例えば、下記が Cartfile.resolved
の一例です。
github "Alamofire/Alamofire" "1.3.0"
github "SwiftyJSON/SwiftyJSON" "2.2.0"
Cartfile
の中の >=
の記述が消え、特定のバージョンが指定されていることがわかります。また、 SwiftyJSON では >= 2.0
を指定していましたが、互換最新リリースである 2.2.0
が checkout されたことがわかります。
この Cartfile.resolved
は Cartfile
を元に生成されるもののため、 ignore してしまうべきもののように思えるかもしれませんが、 Cartfile.resolved
を ignore してはいけません。
Carthage には carthage update
とは別に下記のコマンドが用意されています。
carthage bootstrap
これを使えば Cartfile
から適切なバージョンが選択されるステップを省いて、 Carthage.resolved
で指定されたバージョンがそのまま checkout されます。
carthage update
はライブラリの更新を管理する人だけが実行するようにし、その他のメンバーは carthage bootstrap
を実行するようにすることで、全員が同じバージョンのライブラリを使っていることを保証することができます。
そのため、 Cartfile
に加えて Cartfile.resolved
も ignore せずにコミットに含んでおく必要があるわけです。
.gitignore
Carthage を使う場合の .gitignore
はどのように書けば良いでしょうか。基本的には github/gitignore の Swift.gitignore を使えば良いです。
しかし、この Swift.gitignore
では次のように Carthage/Checkouts
ディレクトリがコメントアウトされており ignore されません。
# Carthage/Checkouts
前述のように、 Carthage.resolved
があればライブラリの特定のバージョンを checkout できるので、基本的には Carthage/Checkouts
をコミットに含める必要はありません。僕は Carthage/Checkouts
を ignore するように変更して( #
を消して)使っています。
ただ、リポジトリにアクセス不能になったり force push されたりといった要因で、今の状態をいつでも完全に復元できるとは限りません。それが許容できないようであれば Carthage/Checkouts
を ignore しないという選択肢もあります。
iOS 7 での Carthage の使い方
iOS 7 では Embedded Binaries を使うことはできません。サブモジュールとしてライブラリを追加し、ソースを直接プロジェクトに追加することが多いと思います。
しかし、それであれば別にサブモジュールでなくてもいいのです。これまで見てきたように、 Carthage/Checkouts
にはライブラリのソースが checkout されているので、 サブモジュールのときと同じように Carthage/Checkouts
のソースをプロジェクトに追加すればいいのです。 Carthage/Build
を使う必要はありません。
そうすれば、 iOS 7 でもライブラリの checkout や依存関係の解決は Carthage に任せることができます。自分でわざわざサブモジュールを管理する必要はありません。
Carthage に対応していないライブラリを Carthage で管理する
Carthage/Build
を使わないのであれば、どんな Git リポジトリでも Carthage で管理することができます(ただし、依存関係の解決まではできません。実際には他のライブラリに依存していないライブラリも多いので、それでも十分役に立ちます)。
例として、僕が作った CGPoint+Vector という、 Carthage 非対応のライブラリを Carthage で checkout してみましょう。 Cartfile
に次の行を足して carthage update
します。
github "koher/CGPoint-Vector" "master"
Carthage に対応していないのでビルドには失敗しますが Carthage/Build
は使わないので問題ありません。 Carthage/Checkouts
にはちゃんとリポジトリが clone されています。あとは、必要なファイルをプロジェクトに直接追加すれば OK です。
また、 Cartfile.resolved
が一度作られれば、 carthage update
や carthage bootstrap
の代わりに次のコマンドを使うことができます。
carthage checkout
このコマンドはライブラリのビルドは行わないので、 Carthage/Build
を使わない場合にはこちらを使うと良いでしょう。
Swift 2.0 / Xcode 7 & iOS 7 & Alamofire
Swift で最も有名なライブラリの一つである Alamofire ですが、 Swift 2.0 / Xcode 7 かつ iOS 7 で使おうとすると次のような問題が起こります。
Swift 2.0 / Xcode 7 用には、 Alamofire の swift-2.0
ブランチを使います。 Cartfile には次のように書きます。
github "Alamofire/Alamofire" "swift-2.0"
carthage update
して Carthage/Checkouts/Alamofire/Source
ディレクトリ下の swift ファイルをプロジェクトに追加すればいいのですが、ビルドしてみると次のようなエラーが発生します。
Carthage/Checkouts/Alamofire/Source/Request.swift:238:55: 'NSQualityOfService' is only available on iOS 8.0 or newer
Carthage/Checkouts/Alamofire/Source/Request.swift:238:36: 'qualityOfService' is only available on iOS 8.0 or newer
これは、 swift-2.0
ブランチでは Deployment Target が iOS 8.0 になっており、 iOS 7.0 では使えない API がここで使われているからです。同じくそれらの API が使えない OSX 10.9 は Deployment Target になっており、そのためにその直前の行で #available
を使ってバージョン判定をしているのでそこに iOS 8.0
を足してやれば解決します。
仕事で Swift 2.0 / Xcode 7 かつ iOS 7 で Alamofire が必要だったので、 Alamofire を fork して Deployment Target を 7.0 に変更し、 #available
に iOS 8.0
を足したバージョンを koher/Alamofire の swift-2.0
ブランチにコミットしてあります。とりあえず使いたい人は↓のようにすれば使えます。
# Swift 2.0 / Xcode 7 かつ iOS 7 で Alamofire を使いたい場合
github "koher/Alamofire" "swift-2.0"
まだ十分に確認ができていないので、確認ができたらプルリクを送るかもです。