问题
Ok, something strange is happening when writing your own equals operator for NSObject subclasses in Swift 2.0 like this:
func ==(lhs: MyObject, rhs: MyObject) -> Bool {
return lhs.identifier == rhs.identifier
}
For a class that looks like this:
class MyObject: NSObject {
let identifier: String
init(identifier: String) {
self.identifier = identifier
}
}
This used to work just fine in Swift 1.2 and below. It still kind of works:
let myObject1 = MyObject(identifier: "A")
let myObject2 = MyObject(identifier: "A")
let result = (myObject1 == myObject2)
// result is true
So far so good, but what if both of the variables were optionals?
let myObject1: MyObject? = MyObject(identifier: "A")
let myObject2: MyObject? = MyObject(identifier: "A")
let result = (myObject1 == myObject2)
// result is false, equals operator was never even called
And one other thing that no longer works:
let myObject1 = MyObject(identifier: "A")
let myObject2 = MyObject(identifier: "A")
let result = (myObject1 == myObject2)
// result is true
let result = (myObject1 != myObject2)
// result is true, equals operator was never even called
So apparently, != no longer calls the == operator and negates it. It seems to just compare the instances instead when using !=
All of this only happens when your class is a subclass of NSObject (directly or indirectly). When it's not, everything works just like you would expect.
Can anyone tell me if this is a new 'feature' in Swift 2.0 or just a nasty bug?
回答1:
Unfortunately I don't know whether this is considered a feature or not (I don't think so). This problem occurs if any class subclasses a class which conforms to Equatable
(like NSObject
; it compares actual instances). So if you only "override" the ==
operator of the subclass all other operators like:
func !=<T : Equatable>(lhs: T, rhs: T) -> Bool
func ==<T : Equatable>(lhs: T?, rhs: T?) -> Bool
func ==<T : Equatable>(lhs: [T], rhs: [T]) -> Bool
where T
is constrained to be Equatable
Swift uses the ==
operator of the baseclass. As (time-consuming) workaround you can overload all the equality operators you have to use like so:
func !=(lhs: MyObject, rhs: MyObject) -> Bool { ... }
func ==(lhs: MyObject?, rhs: MyObject?) -> Bool { ... }
func ==(lhs: [MyObject], rhs: [MyObject]) -> Bool { ... }
Edit: The Reason
The reason for this behavior is that if a subclass conforms to Equatable
the Self
of the self requirement is determined to be this class. So every time the ==
is called with a (generic) type that conforms to Equatable
it only calls the operator of the initial conforming class.
回答2:
I think this behavior should be considered a bug (still present as of Xcode 7 beta 6), but there's a hopefully temporary workaround: override NSObject's -isEqual
instead of implementing Swift's ==
operator.
class MyObject: NSObject {
let identifier: String
init(identifier: String) {
self.identifier = identifier
}
override func isEqual(object: AnyObject?) -> Bool {
guard let rhs = object as? MyObject else {
return false
}
let lhs = self
return lhs.identifier == rhs.identifier
}
}
I found another reference to the problem, with more code examples, here: http://mgrebenets.github.io/swift/2015/06/21/equatable-nsobject-with-swift-2/
回答3:
kylealanhale's answer does not work with NSManagedObject
(explained here), so I created a new protocol NSObjectSubclassEquatable
that you can use for comparing NSobject
subclasses.
infix operator =~= {}
public protocol NSObjectSubclassEquatable {
static func compare(lhs: Self,_ rhs: Self) -> Bool
}
public func =~=<T : NSObjectSubclassEquatable>(lhs: T, rhs: T) -> Bool {
return T.compare(lhs, rhs)
}
func =~=<Element : NSObjectSubclassEquatable>(lhs: [Element], rhs: [Element]) -> Bool {
for (lhsElement,rhsElement) in zip(lhs, rhs) {
if !(lhsElement =~= rhsElement) {
return false
}
}
return true
}
Example:
class Point: NSObject {
var x: Int
var y: Int
init(_ x: Int,_ y: Int) {
self.x = x
self.y = y
}
}
extension Point: NSObjectSubclassEquatable {
static func compare(lhs: Point,_ rhs: Point) -> Bool {
return lhs.x == rhs.x && lhs.y == rhs.y
}
}
Point(1,2) =~= Point(1,2) // Prints true
[Point(1,2)] =~= [Point(1,2)] // Prints true
来源:https://stackoverflow.com/questions/31099379/bug-with-equals-operator-and-nsobjects-in-swift-2-0