web-dev-qa-db-ja.com

SwiftのNSObjectサブクラス:hash vs hashValue、isEqual vs ==

SwiftでNSObjectをサブクラス化するとき、ハッシュをオーバーライドするか、Hashableを実装する必要がありますか?また、isEqual:をオーバーライドするか、==を実装する必要がありますか?

35
user4691305

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

公式のリファレンスは見つかりませんでしたが、hashValuehashからNSObjectProtocolメソッドを呼び出し、==isEqual:メソッドを呼び出しているようです(同じプロトコルから)。 回答の最後に更新を参照してください!

NSObjectサブクラスの場合、正しい方法はhashおよびisEqual:をオーバーライドすることであるように思われます。以下はそれを実証する実験です。

1. 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

ご覧のとおり、NSSetSetの両方がオブジェクトを異なるものとして扱います。これは望ましい結果ではありません。配列にも予期しない結果があります。

let nsArrayA = NSArray(object: a1)
let swArrayA = [a1]

print(nsArrayA.indexOfObject(a2)) // 9223372036854775807 == NSNotFound
print(swArrayA.indexOf(a2)) // nil

ブレークポイントを設定するか、デバッグ出力を追加すると、オーバーライドされた==演算子が呼び出されないことが明らかになります。これがバグなのか、意図した動作なのかはわかりません。

2. 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:メソッドを実装する必要があります。

クラスに平等を実装する一環として、オブジェクト比較のルールに従ってハッシュプロパティを実装してください。

91
Martin R

Hashableを実装します。これには、タイプに==演算子を実装する必要もあります。これらは、indexOfを実装する型のコレクションでのみ動作するEquatable関数のようなSwift標準ライブラリの多くの便利なものに使用されます。 Hashableを実装する型でのみ機能するSet<T>型。

0
Kametrixom