2015/06/18

SwiftのStringInterpolationConvertibleプロトコルで文字列出力をカスタマイズする

プロトコルStringInterpolationConvertibleの使いかたがようやくわかったので簡単に紹介します。

StringInterpolationConvertibleとは

String Interpolationとは簡単に言うと、文字列の中の\(コレ)のことです。

つまり、StringInterpolationConvertibleとは、 String Interpolationを含む文字列をコンバートできるようにしたクラス、ということになります。

StringInterpolationConvertibleプロトコルの実装例

StringInterpolationConvertibleプロトコルの実装すると、次のように数値に16進数を併記させるといったことも可能になります。

print("\(777) is a magical number!" as HexNumberString)
//=> "777 (0x309) is a magical number!"

print("\(0xdead_beaf), \(UInt(777))" as HexNumberString)
//=> "3735928495 (0xDEADBEEF), 777"

実装は次のような感じ (解説は後ほど)。

final class HexNumberString : StringInterpolationConvertible, Printable {
    let s: String

    required init<T>(stringInterpolationSegment expr: T) {
        s = "\(expr)"
    }
    required init(stringInterpolationSegment expr: Int) {
        let hex = String(NSString(format:"%X", expr))
        s = "\(expr) (0x\(hex))"
    }

    required init(stringInterpolation strings: HexNumberString...) {
        s = "".join(strings.map { $0.s })
    }

    var description: String {
        return s
    }
}

StringInterpolationConvertibleプロトコルの処理手順

  1. 与えられた文字列は、String Interpolationの部分と地の文字列とに分けられます。
  2. 先頭から順番にinit(stringInterpolationSegment)が呼ばれて、それぞれのインスタンスが作成されます。
  3. 作成したインスタンスをまとめた配列でinit(stringInterpolation strings: HexNumberString...)が呼ばれます

例えば、"abc\(1 + 30)" as HexNumberStringでは、まず

  • String型の "abc"
  • (評価後の) Int型の 31
  • String型の ""

の3つに分かれます。

1つめの"abc"と3つめの""に対してはinit<T>(stringInterpolationSegment: T)が呼ばれますが、2つめの31にはinit(stringInterpolationSegment: Int)が呼ばれます。

これによって、3つのHexNumberString型のインスタンス (内部変数sは順番に"abc", "31 (0x1F)", "") が作成されます。

そして最後に、この3つのインスタンスをまとめた配列を引数として、init(stringInterpolation strings: HexNumberString...)が呼ばれます。

StringInterpolationConvertibleプロトコルの実装の手引き

汎用型のinit<T>(stringInterpolationSegment: T)を実装する以外にも、 自分がカスタマイズしたい型をinit(stringInterpolationSegment: SomeClass)として実装します。

上の例では16進数を表示させたいInt型について実装しています。

汎用型のinit<T>(stringInterpolationSegment: T)は地のStringを含むことがあるので何もしないのが無難でしょう。 同様に、init<T>(stringInterpolationSegment: String)では地のStringを含むことがあるので注意です。

StringInterpolationConvertibleは実装クラスがfinalでないと次のようなエラーになるのでfinalしておきます。

Protocol ‘StringInterpolationConvertible’ requirement ‘init(stringInterpolation:)’ cannot be satisfied by a non-final class (‘MySample’) because it uses ‘Self’ in a non-parameter, non-result type position

Printable (Swift 2ではCustomStringConvertible) を実装しないとprintでクラス名が表示されるだけになるので実装します。

応用編

数値をローマ数字や漢数字に変換するクラスや、次のような比較用タプルが使えるようにしたDiffStringというのも考えられます。

required init<Eq: Equatable>(stringInterpolationSegment expr: (Eq, Eq)) {
    let cmp = expr.0 == expr.1 ? "==" : "!="
    s = "\(expr.0) \(cmp) \(expr.1)"
}

print("\(1,2)" as DiffString)
//=> 1 != 2

print("\(5,5)" as DiffString)
//=> 5 == 5

他にも便利そうな応用例がありましたら教えてください。

関連リンク

0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。