JavaScriptCore の JSValue を大小比較できるようにする
Swift プログラミング
JavaScriptCore の JSValue を Swift ネイティブコードからでも大小比較できるように JSValue を Comparable に準拠させてみました。
Swift で JavaScriptCore
の JSValue
を Comparableプロトコル
に準拠させて大小比較できるようにしたかったんですけど、どうやら現在の JavaScriptCore
には JSValue を簡単に比較するための関数が用意されていないようでした。
等価比較であれば JSValueIsEqual
関数があるのですけど、これと同じように使える関数がないみたいなんですよね。JavaScript でなら普通に演算子で大小比較できるので、それと同じ機能がネイティブにも用意されていたらよかったのですけど。
そんなことを思ってみたら、そもそも JavaScriptCore
ですし、それなら JavaScript で大小比較した結果を返すネイティブ関数を作ってしまえばいいんじゃないかと思い立ち、さっそく作ってみることにしました。
JSValue で大小比較を可能にする
compare メソッドを実装する
JSValue
は NSObject
から継承したクラスなので、ここで compare:メソッド
を実装してあげれば、NSObject
にインターフェイスだけ用意されている isGreaterThan:
などの大小比較を行うためのメソッドを利用できるようになります。
この compare:メソッド の中で JavaScript で比較した結果を返すようにしてみます。
extension JSValue {
func compare(object:JSValue) -> Int {
let context = JSContext()
context.setObject(self, forKeyedSubscript: "lhs")
context.setObject(object, forKeyedSubscript: "rhs")
let result = context.evaluateScript("if (lhs == rhs) { 0 } else { lhs < rhs ? -1 : 1 }")
return Int(result.toInt32())
}
}
これで、ある JSValue型 の値に対して、別の JSValue型 の値を添えて compare:メソッド を呼び出すことで、値が一致すれば 0 を、自身の方が小さければ -1 を、大きければ 1 を返すという計算を JavaScript で実行して、その結果をそのままネイティブ側で得ることができるようになりました。
こうすることで、これに連動して NSObject
に実装されている isGreaterThan:メソッド
や isGreaterThanOrEqualTo:メソッド
、isLessThan:メソッド
、isLessThanOrEqualTo:メソッド
、といったメソッドが使えるようになります。
演算子で大小比較できるようにする
Swift で大小比較するときは、メソッドを使うよりも演算子を使った方がそれらしいので、Comparableプロトコル に準拠させることにします。
extension JSValue : Comparable {
}
public func < (lhs:JSValue, rhs:JSValue) -> Bool {
return lhs.isLessThan(rhs)
}
public func > (lhs:JSValue, rhs:JSValue) -> Bool {
return lhs.isGreaterThan(rhs)
}
public func <= (lhs:JSValue, rhs:JSValue) -> Bool {
return lhs.isLessThanOrEqualTo(rhs)
}
public func >= (lhs:JSValue, rhs:JSValue) -> Bool {
return lhs.isGreaterThanOrEqualTo(rhs)
}
また、Comparableプロトコル
は Equatable
にも準拠させる必要があります。
継承元の NSObject
自身は既に Equatable
に準拠しているのですけど、継承先の JSValue
では正しく比較できるようになっていない様子なので、正しく動作するように、併せて isEqual:メソッド
をオーバーライドしておく必要があります。
等価比較については JavaScript の力を借りなくても JSValueIsEqual関数 があらかじめ用意されているので、それを使って実現します。
extension JSValue {
override public func isEqual(object: AnyObject?) -> Bool {
if let value = object as? JSValue {
return self.isEqualToValue(value)
}
else {
return super.isEqual(object)
}
}
public func isEqualToValue(object: JSValue?) -> Bool {
if let object = object {
return JSValueIsEqual(self.context.JSGlobalContextRef, self.JSValueRef, object.JSValueRef, nil)
}
else {
return false
}
}
}
このようにすることで JSValue
同士での大小比較が、ネイティブから直接できるようになりました。
let context = JSContext()
let value1 = JSValue(int32: 100, inContext: context)
let value2 = JSValue(double: 200.5, inContext: context)
value1 < value2
value1 > value2
value1 <= value2
value1 >= value2