Mirrorを使ってそのインスタンスの情報を返す

Wednesday, October 5, 2016

目新しいものでもなく、あちこちで見かける内容ですが。
Mirror を使うとそのインスタンスが持つ情報を簡単に取得できるので、デバッグ時にはかなり助かります。
Swift3.0向けに extension にてより扱いやすい形で書いてみました。

実装

protocol Reflectable {
}

extension Reflectable where Self: Any {
    private func toString(_ child: Mirror.Child) -> String? {
        guard let label = child.label else {
            return nil
        }
        return "\(label): \(child.value)"
    }

    var reflected: String {
        return Mirror(reflecting: self).children.flatMap(toString).joined(separator: "\n")
    }
}

Swift3.0で Any が変わったのもあって、 extension 時の束縛で where Self: Any と書けるようになって、関数の中で self が使えるようになったのが地味にいい感じです。
extension の名前悩みましたが一旦 Reflectable にしました。(前そんな名前Swiftの標準であったような…)

使用例

使用したいclassやstuct、enumに Reflectable を適合させて、 reflected を呼び出すだけです。
また、下記の Fuga のように、 CustomStringConvertible を適合させて、
var description: String の中で reflected を呼び出せば、 print したときに、自分自身の情報をそのまま出力することができます。

struct Hoge: Reflectable {
    let a: Int
    let b: String
    let c: [Int]
    let ddd: (String, Int)
    let e: Fuga?
}

struct Fuga: CustomStringConvertible, Reflectable {
    let x: Double

    var description: String {
        return "@\(reflected)@"
    }
}


let hoge: Hoge = Hoge(a: 100, b: "test", c: [100, 200, 300], ddd: ("Hoge", 123), e: Fuga(x: 3.14))
print(hoge.reflected)
// => "a: 100\\nb: test\\nc: [100, 200, 300]\\nddd: (\\"Hoge\\", 123)\\ne: Optional(@x: 3.14@)"

これでデバッグ中に変数の中身とかを出す時は助かりますね。

さらに発展させる(filterできるように)

全て表示されるのは…というのがあるので、
特定のものだけ出力したい特定のものは除外したい という場合は、以下のように書き換えます。

protocol Reflectable {
}

extension Reflectable where Self: Any {
    private func toString(_ child: (String, Any)) -> String {
        return "\(child.0): \(child.1)"
    }

    private var list: [(String, Any)] {
        return Mirror(reflecting: self).children.flatMap { $0.label != nil ? ($0.label!, $0.value) : nil }
    }

    var reflected: String {
        return list.map(toString).joined(separator: "\n")
    }

    func reflected(includes: [String]) -> String {
        return list.filter { includes.contains($0.0) }.map(toString).joined(separator: "\n")
    }

    func reflected(excludes: [String]) -> String {
        return list.filter { !excludes.contains($0.0) }.map(toString).joined(separator: "\n")
    }
}

これで、

let hoge: Hoge? = Hoge(a: 100, b: "test", c: [100, 200, 300], ddd: ("Hoge", 123), e: Fuga(x: 3.14))
print(hoge?.reflected)
// => "a: 100\nb: test\nc: [100, 200, 300]\nddd: ("Hoge", 123)\ne: Optional(@x: 3.14@)"
print(hoge?.reflected(includes: ["a", "b"]))
// => "a: 100\nb: test"
print(hoge?.reflected(excludes: ["a"]))
// => "b: test\nc: [100, 200, 300]\nddd: ("Hoge", 123)\ne: Optional(@x: 3.14@)"

といった感じで出力できるようになります。

どうしてもkey部を文字列でハードコーディングする必要がありますが、そこまで苦にはならないかと…!
これでより扱いやすくなるかと思います。

techSwiftSwift3.0Debug

【Swift3】navigationControllerのpopViewControllerで警告がでる

Swift3への移行時に役に立ちそうなXcodeのOption