My Favorite Things - Coding or die.

とある技術者の経験記録、的な。

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.printlncarthageはモジュール名を指しているらしい。 つまり「モジュール名.関数」という形でアクセスできるというわけだ。(常識だったりする?)

最初はどこかにインスタンスが定義されているものと思ったが、そもそも実体は関数であるわけで。

元の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」コマンドがどのように処理されているのかを見てみた。 単純な実装ではあったが、コマンド実行の仕組み、ちょっとした工夫が見られたような気がする。

はてさて、こんなペースで続けていてコードリーディングが終わるのか不安である。 次回に続く・・・のか?

文章中の表現が統一できていない箇所が多いですけれど、そのうち揃えていきますのでご容赦を。