Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift 2.0 Set not working as expected when containing NSObject subclass

After upgrading our codebase to Swift2 I've encountered unusual problem. Set is not substracting nor unioning as expected.

class A: NSObject {
    let h: Int

    init(h: Int) {
        self.h = h
    }

    override var hashValue: Int {
        return h
    }
}

func ==(lhs: A, rhs: A) -> Bool {
    return lhs.hashValue == rhs.hashValue
}

let a = A(h: 1)
let b = A(h: 1)

var sa = Set([a])
let sb = Set([b])

sa.subtract(sb).count // Swift1.2 prints 0, Swift 2 prints 1

sa.contains(a) // Swift1.2 true, Swift 2 true
sa.contains(b) // Swift1.2 true, Swift 2 false

It looks like new Set is not using hashValue for internal operations. Any idea is that a bug, or a way to workaround this issue?

like image 978
muvaaa Avatar asked Sep 22 '15 20:09

muvaaa


2 Answers

I played with your code a bit. I was able to get it working by no longer subclassing NSObject, but instead conforming to the Hashable protocol:

class A: Hashable {
    let h: Int

    init(h: Int) {
        self.h = h
    }

    var hashValue: Int {
        return h
    }

}

func ==(lhs: A, rhs: A) -> Bool {
    return lhs.hashValue == rhs.hashValue
}

let a = A(h: 1)
let b = A(h: 1)

var sa = Set([a])
let sb = Set([b])

sa.subtract(sb).count // Swift1.2 prints 0, Swift 2 prints 1

sa.contains(a) // Swift1.2 true, Swift 2 true
sa.contains(b) // Swift1.2 true, Swift 2 false

a.hashValue == b.hashValue

When you were inheriting from NSObject, your == overload wasn't actually being executed. If you want this to work with NSObject, you'd have to override isEquals:

override func isEqual(object: AnyObject?) -> Bool {
    if let object = object as? A {
        return object.h == self.h
    } else  {
        return false
    }
}
like image 133
TheRobDay Avatar answered Nov 17 '22 15:11

TheRobDay


//: Playground - noun: a place where people can play

import Foundation

class X: NSObject {
    var x:String
    var y:Int

    init(x:String, y:Int) {
        self.x = x
        self.y = y
        super.init()
    }

    override var hashValue: Int {
        return x.hashValue ^ y.hashValue
    }

    override func isEqual(object: AnyObject?) -> Bool {
        if let rhs = object as? X {
            return x == rhs.x && y == rhs.y
        } else {
            return false
        }
    }
}

func == (lhs:X, rhs:X) -> Bool {
    return lhs.isEqual(rhs)
}

let x = X(x: "x1", y: 1)
let y = X(x: "x1", y: 1)
X(x: "x1", y: 1) == X(x: "x1", y: 1) // Swift 'x == y' (true)
x.isEqual(y)                         // Obj-C '[x isEqual: y]' (true)

var s:Set<X> = [X(x: "x1", y: 1)]
s.count // count == 1
s.insert(X(x: "x2", y: 1))
s.count // count == 2
s.insert(X(x: "x1", y: 1))
s.count // count == 2
s.insert(X(x: "x2", y: 1))
s.count // count == 2

I spent time looking for the right answer to this until I hit this Question/Answer. I had reverted to basics in XCode Playground to see what was happening. Using subclasses of NSObject in Swift Set makes a bunch of more readable code.

like image 28
CPD Avatar answered Nov 17 '22 15:11

CPD