iOS 7 でも Carthage を使おう

  • 85
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

本投稿では Swift のライブラリ管理に欠かせない Carthage について、

  1. Carthage の基本的な使い方
  2. iOS 7 での Carthage の使い方

を説明します。

はじめに

ライブラリの依存関係を解決し管理するのは大変な作業です。 Swift にはライブラリの依存管理ツールとして主に CarthageCocoaPods という選択肢があります。

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 以降の最新リリースをインストールするには次のように書きます。とても簡単ですね。

Cartfile
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. >= 1.0 for “at least version 1.0”
  2. ~> 1.0 for “compatible with version 1.0”
  3. == 1.0 for “exactly version 1.0”
  4. "some-branch-or-tag-or-commit" for a specific Git object (anything allowed by git rev-parse)

>= を使えば、指定したバージョン以降の最新のバージョンが、 ~> を使えば指定したバージョンと 互換性がある 最新のバージョンが、 == を使えば指定した特定のバージョンがインストールされます。

それ以外に、 4 で書かれているように特定のブランチやコミットを指定することもできます(下記は PromiseKexperiment ブランチを指定する例です)。

Cartfile
github "koher/PromiseK" "experiment"

ブランチの代わりにコミット番号を書けば特定のコミットを指定することができます。

Semantic Versioning

~> を使えば指定したバージョンと互換性がある最新のバージョンがインストールされると書きましたが、互換性はどのように検証されているのでしょうか。実は、バージョンの付け方に秘密があります。

Carthage 対応のライブラリは Semantic Versioning というバージョンの付け方をする必要があります。 Semantic Versioning では、 1.2.3 というバージョンがあるとすると、 1 の部分が MAJOR version2 の部分が MINOR version3 の部分が 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 の一例です。

Cartfile.resolved
github "Alamofire/Alamofire" "1.3.0"
github "SwiftyJSON/SwiftyJSON" "2.2.0"

Cartfile の中の >= の記述が消え、特定のバージョンが指定されていることがわかります。また、 SwiftyJSON では >= 2.0 を指定していましたが、互換最新リリースである 2.2.0 が checkout されたことがわかります。

この Cartfile.resolvedCartfile を元に生成されるもののため、 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/gitignoreSwift.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 します。

Cartfile
github "koher/CGPoint-Vector" "master"

Carthage に対応していないのでビルドには失敗しますが Carthage/Build は使わないので問題ありません。 Carthage/Checkouts にはちゃんとリポジトリが clone されています。あとは、必要なファイルをプロジェクトに直接追加すれば OK です。

また、 Cartfile.resolved が一度作られれば、 carthage updatecarthage 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 には次のように書きます。

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 に変更し、 #availableiOS 8.0 を足したバージョンを koher/Alamofireswift-2.0 ブランチにコミットしてあります。とりあえず使いたい人は↓のようにすれば使えます。

Cartfile
# Swift 2.0 / Xcode 7 かつ iOS 7 で Alamofire を使いたい場合
github "koher/Alamofire" "swift-2.0"

まだ十分に確認ができていないので、確認ができたらプルリクを送るかもです。