JavaScriptCore の JSValue を大小比較できるようにする

Swift プログラミング

JavaScriptCore の JSValue を Swift ネイティブコードからでも大小比較できるように JSValue を Comparable に準拠させてみました。


Swift で JavaScriptCoreJSValueComparableプロトコル に準拠させて大小比較できるようにしたかったんですけど、どうやら現在の JavaScriptCore には JSValue を簡単に比較するための関数が用意されていないようでした。

等価比較であれば JSValueIsEqual 関数があるのですけど、これと同じように使える関数がないみたいなんですよね。JavaScript でなら普通に演算子で大小比較できるので、それと同じ機能がネイティブにも用意されていたらよかったのですけど。


そんなことを思ってみたら、そもそも JavaScriptCore ですし、それなら JavaScript で大小比較した結果を返すネイティブ関数を作ってしまえばいいんじゃないかと思い立ち、さっそく作ってみることにしました。

JSValue で大小比較を可能にする

compare メソッドを実装する

JSValueNSObject から継承したクラスなので、ここで 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