今更ながら、try! Swiftで紹介されていたValueTransformerが正しく動作するように直してみた

Thursday, April 14, 2016

巷がRxSwiftで盛り上がっている中、昨日try! Swiftの発表のスライドを見返していました。
そんな中、このスライドで挙げられている、NSValueTransformerをswiftyに扱う手法が紹介されていたのですが、
これがスライド通りにコードを書いても うまく動いてくれない ので、動くように修正してみました。
動かないのが嫌だった…

修正してみた

元のコードは、スライド内にかかれているので省略します。

class ValueTransformer<A, B>: NSValueTransformer {
    typealias Transform = A? -> B?
    typealias ReverseTransform = B? -> A?
    private let transform: Transform
    private let reverseTransform: ReverseTransform

    init(transform: Transform, reverseTransform: ReverseTransform) {
        self.transform = transform
        self.reverseTransform = reverseTransform
        super.init()
    }

    override static func transformedValueClass() -> AnyClass {
        return AnyObject.self
    }

    override class func allowsReverseTransformation() -> Bool {
        return true
    }

    override func transformedValue(value: AnyObject?) -> AnyObject? {
        return transform(value as? A).flatMap { $0 as? AnyObject }
    }

    override func reverseTransformedValue(value: AnyObject?) -> AnyObject? {
        return reverseTransform(value as? B).flatMap { $0 as? AnyObject }
    }
}

変更点としては、

  • <A, B> にかかっていたAnyObjectの制約を外した
  • transformedValueClass() で返す型をAnyObject.selfに変更
  • transform, reverseTransform で帰ってきた値を flatMap を使ってAnyObject?に変換するように変更

になります。これで、スライドにあった例の通りに動作します。

// ValueTransformer<String, NSUUID> と明記してもok!
let transformer = ValueTransformer(transform: {
    return NSUUID(UUIDString: $0 ?? "")
    }, reverseTransform: {
        return $0?.UUIDString
})

let uuid = transformer.transformedValue(NSUUID().UUIDString)
_ = transformer.reverseTransformedValue(uuid)

もちろん、AnyObjectであることが条件になるので、NSValueTransformerを拡張したこの ValueTransformer は、structやenumの型には適応できません。

もし、変換のclosureを渡すことで任意の型から型への変換をするなら、ikesyoさんの作ったHimotoki内にあるTransformerを参考に作ってみるといいのかなって思いました。
ここにあるTransformerは単方向ですが、NSValueTransformerのように逆方向もサポートしたら面白そう。

techSwiftTipstry!Swift

2つのDictionaryを結合するextensionと演算子を書く

Carthageで特定のライブラリだけupdateする