Swift で NSObject を継承したオブジェクトの等価演算子を拡張する

Swift プログラミング

Swift で JSValue どうしを等価比較できるようにと思って == 演算子を実装してみたところ、ジェネリック関数で正しく動いてくれませんでした。

今回のように NSObject を継承したオブジェクトでは、等価演算子を直接実装するのではなく isEqual メソッドをオーバーライドすることで実現する必要があります。


たとえば JavaScriptCoreJSType型 を Swift で等価演算子を使って比較できるようにしたいと思ったときに、素直に Equatable に準拠させようとすると、次のエラーでビルドできませんでした。

Redundant conformance of 'JSValue' to protocol 'Equatable'

これは JSValue が継承している NSObject が既に Equatable に準拠しているためなのでしょう。


だからといって、そのまま次のように定義しても、うまく使えない場合がでてきます。

func == (lhs:JSValue, rhs:JSValue) -> Bool {

}

直接 JSValue の値どうしを比較すればちゃんと動くのですけど、たとえば Swift の XCTestAssertEqual 関数のように Equatable を想定したジェネリック関数の場合だと、本来の Equatable に準拠している NSObject 用の == 演算子が呼び出されてしまい、上の実装は無視されてしまいます。

NSObject を継承したオブジェクトで == を実装する

どうしたものかとつい考えてしまったんですが、そういえば Objective-C のときには当たり前にやっていた方法で、Swift で == 演算子を使った比較を実装することができました。

やり方は Objective-C ではごく当たり前な方法で、JSValue で isEqual: メソッドをオーバーライドしてあげます。

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 {

		return JSValueIsEqual(self.context.JSGlobalContextRef, self.JSValueRef, object.JSValueRef, nil)
	}
}

そうすると、JSValue 用に == を実装しなくても、JSValue 同士の比較が行われて NSObject 用の == が実行されたときに、この JSValue でオーバーライドした isEqual: メソッドが呼び出されます。

これで Equatable を想定したジェネリック関数に JSValue の値を渡したときにも、正しく等価判定ができるようになりました。