Carthageコードリーディング::2日目
今回は一番簡単であろう「version」コマンドを見ていく。
VersionCommand
登録
前回も触れたが、main.swiftでCommandRegistryに各コマンドを登録している。
// main.swift let registry = CommandRegistry<CarthageError>() registry.register(VersionCommand())
「version」コマンドは「VersionCommand」という構造体を生成して登録している。
VersionCommandの定義
実際の定義を見てみると、以下のようになっている。
public struct VersionCommand: CommandType { public let verb = "version" public let function = "Display the current version of Carthage" public func run(options: NoOptions<CarthageError>) -> Result<(), CarthageError> { let versionString = NSBundle(identifier: CarthageKitBundleIdentifier)?.objectForInfoDictionaryKey("CFBundleShortVersionString") as! String carthage.println(versionString) return .Success(()) } }
「CommandType」というプロトコルを実装しているシンプルな構造体になっている。
見たままだが、定義されている定数とメソッドは以下のようになっている。
- verb・・・コマンド名
- function・・・コマンドのusage
- run()・・・実際に実行する処理
「verb」と「function」で定義された文字列は「carthage help」で確認できる。
$ carthage help
Available commands:
...
version Display the current version of Carthage
「run()」の処理結果はもちろん「carthage version」で確認できる。
$ carthage version 0.15.2
これでとりあえず「VersionCommand」の処理内容は全て確認できたはずである。
run()の中身
run()の中身をもう一度見てみる。
public func run(options: NoOptions<CarthageError>) -> Result<(), CarthageError> { let versionString = NSBundle(identifier: CarthageKitBundleIdentifier)?.objectForInfoDictionaryKey("CFBundleShortVersionString") as! String carthage.println(versionString) return .Success(()) }
最初はバージョン番号の取得だが、よく使われる(と私が思っている)NSBundle.mainBundle()
は使われていない。
CarthageKitBundleIdentifier
の定義を見てみる。
/// Carthage’s bundle identifier. public let CarthageKitBundleIdentifier = NSBundle(forClass: Project.self).bundleIdentifier!
Projectクラスが所属するBundleのbundleIdentifier
を取得するようになっているようだ。
正確な意図がわからなかったが、Frameworkなどでプロジェクトを分割した時でも正しいplistが参照できるようにするためだろうか?
ちなみに強制アンラップ(!)や強制キャスト(as!)が使用されているのには興味深い。
プロジェクトの開発ルールによっては一部を除外してほぼ禁止されることも多いが、必ず成功するという確信が得られるケースでは利用しているようだ。 (これは個人的にも賛成だ)
次はバージョン番号を出力しているコードであるが、carthage.println
なるメソッドが利用されている。
carthage.println(versionString)
そしてどうやら定義はExtensions.swift
に記述された以下関数らしい。
/// A thread-safe version of Swift's standard println(). internal func println<T>(object: T) { dispatch_async(outputQueue) { Swift.print(object) } }
標準のprint()
がスレッドセーフでないので、スレッドセーフなバージョンを用意しているようだ。
(コメントに書いてあるまんまですね)
そして私は初めて知ったのだが、carthage.println
のcarthage
はモジュール名を指しているらしい。
つまり「モジュール名.関数」という形でアクセスできるというわけだ。(常識だったりする?)
最初はどこかにインスタンスが定義されているものと思ったが、そもそも実体は関数であるわけで。
元のrun()メソッドに戻り、最後は結果を返している。
return .Success(())
これは「Result」モジュールの「Result」が利用されているらしい。
/// An enum representing either a failure with an explanatory error, or a success with a result value. public enum Result<T, Error: ResultErrorType>: ResultType, CustomStringConvertible, CustomDebugStringConvertible { case Success(T) case Failure(Error)
Haskell使いには「Either」といったほうが早いだろうか? つまり今回のケースでは処理としては「成功」で、その中身は「空タプル」というわけだ。
CommandTypeプロトコル
これで一応はrun()の中身が読み終わったので、CommandTypeプロトコルを見てみる。
/// Represents a subcommand that can be executed with its own set of arguments. public protocol CommandType { /// The command's options type. associatedtype Options: OptionsType associatedtype ClientError: ClientErrorType = Options.ClientError /// The action that users should specify to use this subcommand (e.g., /// `help`). var verb: String { get } /// A human-readable, high-level description of what this command is used /// for. var function: String { get } /// Runs this subcommand with the given options. func run(options: Options) -> Result<(), ClientError> }
ここまでくれば目新しいものは無い。 VersionCommandで実装されていた定数verbとfunction、run()がプロトコルとして定義されている。
つまりすべてのCarthageコマンドは、このCommandTypeプロトコルを実装することになる。
もし自分で新しいコマンドを作りたい場合(がもしあったとしてだが)、 このプロトコルを実装したクラスあるいは構造体を作ってCommandRegistryに登録しておけばいいわけだ。 (なんかオブジェクト指向の授業みたいになっちゃったけど、まぁそれはさておき)
ちなみにCommandTypeは「Commandant」モジュールに含まれている。 これは(おそらく)Carthageのコマンド引数のパースや実行の仕組みが「Commandant」モジュールで実現されていることを示唆する。
まとめ
今回は一番簡単であろう「version」コマンドがどのように処理されているのかを見てみた。 単純な実装ではあったが、コマンド実行の仕組み、ちょっとした工夫が見られたような気がする。
はてさて、こんなペースで続けていてコードリーディングが終わるのか不安である。 次回に続く・・・のか?
文章中の表現が統一できていない箇所が多いですけれど、そのうち揃えていきますのでご容赦を。