しおメモ

雑多な技術系ブログです。ニッチな内容が多いです。

NSObjectを継承したクラスでEquatableの操作をする

NSObjectは自身がEquatableなので、==Equatableに対するメソッドが呼べたりするのですが、デフォルトの動作はポインタを比較するSwiftでいう===相当なので気をつけましょう。

final class Hoge: NSObject {
    let value: Int

    init(_ value: Int) {
        self.value = value
    }
}

print(Hoge(1) == Hoge(1)) // false

インスタンスとしては別のものなので、上記コードの比較の結果はfalseになります。

解決策

NSObjectを継承したクラスでは、比較の際にNSObjectProtocolisEqualが呼ばれるので、そちらをoverrideすることで、意図した比較を行うことができます。
valueで比較したい場合は以下のようになります。

override func isEqual(_ object: Any?) -> Bool {
    guard let other = object as? Hoge else {
        return false
    }

    return value == other.value
}

NSObjectHashableでもあるのでそちらも整合させるようにします。(オブジェクトの==が成り立つ場合、hashValueも同じでなければなりません。) isEqualと同様に、hashをoverrideすることで、Hashableで使われるハッシュ値を変更することができます。

override var hash: Int {
    var hasher = Hasher()
    value.hash(into: &hasher)
    return hasher.finalize()
}

全体のコードはこのようになります。

final class Hoge: NSObject {
    let value: Int

    init(_ value: Int) {
        self.value = value
    }

    override var hash: Int {
        var hasher = Hasher()
        value.hash(into: &hasher)
        return hasher.finalize()
    }

    override func isEqual(_ object: Any?) -> Bool {
        guard let other = object as? Hoge else {
            return false
        }

        return value == other.value
    }
}

let hoge1 = Hoge(1)
let hoge2 = Hoge(1)
print(hoge1 == hoge2) // true
print(hoge1.hashValue == hoge2.hashValue) // true

雑記

転職して結構コード書いてたので、ストレスが減って最近ブログ書いてませんでした。

無駄なNSObject継承はやめよう 👊