SwiftでNSObjectをサブクラス化するとき、ハッシュをオーバーライドするか、Hashableを実装する必要がありますか?また、isEqual:をオーバーライドするか、==を実装する必要がありますか?
NSObject
はすでにHashable
プロトコルに準拠しています:
extension NSObject : Equatable, Hashable {
/// The hash value.
///
/// **Axiom:** `x == y` implies `x.hashValue == y.hashValue`
///
/// - Note: the hash value is not guaranteed to be stable across
/// different invocations of the same program. Do not persist the
/// hash value across program runs.
public var hashValue: Int { get }
}
public func ==(lhs: NSObject, rhs: NSObject) -> Bool
公式のリファレンスは見つかりませんでしたが、hashValue
はhash
からNSObjectProtocol
メソッドを呼び出し、==
はisEqual:
メソッドを呼び出しているようです(同じプロトコルから)。 回答の最後に更新を参照してください!
NSObject
サブクラスの場合、正しい方法はhash
およびisEqual:
をオーバーライドすることであるように思われます。以下はそれを実証する実験です。
hashValue
および==
をオーバーライドしますclass ClassA : NSObject {
let value : Int
init(value : Int) {
self.value = value
super.init()
}
override var hashValue : Int {
return value
}
}
func ==(lhs: ClassA, rhs: ClassA) -> Bool {
return lhs.value == rhs.value
}
ここで、「等しい」と見なされるクラスの2つの異なるインスタンスを作成し、セットに入れます。
let a1 = ClassA(value: 13)
let a2 = ClassA(value: 13)
let nsSetA = NSSet(objects: a1, a2)
let swSetA = Set([a1, a2])
print(nsSetA.count) // 2
print(swSetA.count) // 2
ご覧のとおり、NSSet
とSet
の両方がオブジェクトを異なるものとして扱います。これは望ましい結果ではありません。配列にも予期しない結果があります。
let nsArrayA = NSArray(object: a1)
let swArrayA = [a1]
print(nsArrayA.indexOfObject(a2)) // 9223372036854775807 == NSNotFound
print(swArrayA.indexOf(a2)) // nil
ブレークポイントを設定するか、デバッグ出力を追加すると、オーバーライドされた==
演算子が呼び出されないことが明らかになります。これがバグなのか、意図した動作なのかはわかりません。
hash
およびisEqual:
をオーバーライドしますclass ClassB : NSObject {
let value : Int
init(value : Int) {
self.value = value
super.init()
}
override var hash : Int {
return value
}
override func isEqual(object: AnyObject?) -> Bool {
if let other = object as? ClassB {
return self.value == other.value
} else {
return false
}
}
}
Swift 3、の場合、isEqual:
の定義は
override func isEqual(_ object: Any?) -> Bool { ... }
これで、すべての結果が期待どおりになりました。
let b1 = ClassB(value: 13)
let b2 = ClassB(value: 13)
let nsSetB = NSSet(objects: b1, b2)
let swSetB = Set([b1, b2])
print(swSetB.count) // 1
print(nsSetB.count) // 1
let nsArrayB = NSArray(object: b1)
let swArrayB = [b1]
print(nsArrayB.indexOfObject(b2)) // 0
print(swArrayB.indexOf(b2)) // Optional(0)
更新:この動作は、「CocoaでのSwiftの使用」の Objective-C APIとの対話 に文書化されました。およびObjective-C」リファレンス:
NSObjectクラスはID比較のみを実行するため、NSObjectクラスから派生したクラスに独自のisEqual:メソッドを実装する必要があります。
クラスに平等を実装する一環として、オブジェクト比較のルールに従ってハッシュプロパティを実装してください。
Hashable
を実装します。これには、タイプに==
演算子を実装する必要もあります。これらは、indexOf
を実装する型のコレクションでのみ動作するEquatable
関数のようなSwift標準ライブラリの多くの便利なものに使用されます。 Hashable
を実装する型でのみ機能するSet<T>
型。