I am getting frustrated with how Swift handles equality. Or I'm just missing something. How come these 2 indexOf DOESN'T work the same way?
let first = self.objects.indexOf(object) //This returns nil
let second = self.objects.indexOf{$0 == object} //This returns the index
My == override:
func ==(lhs: MyObject, rhs: MyObject) -> Bool {
return lhs.someProperty == rhs.someProperty
}
The == override doesn't get called in the first indexOf. Why is that so? This feels really dangerous.
(MyObject is a subclass of PFObject (Parse.com objects). I don't know if this is what's messing this up.)
Since your Object is subclass of PFObject, and PFObject is subclass of NSObject, and NSObject already confirm to Equatable by using the isEqual method to implement. So your == operator override is not working. You should override the isEqual method. (To be honest, it's horrible :(
sample code:
class Object: NSObject {
var value: Int
init(value: Int) {
self.value = value
}
override func isEqual(object: AnyObject?) -> Bool {
guard let obj = object as? Object else { return false }
return self.value == obj.value
}
}
// If your class is not inherit from any ObjC class:
//extension Object: Equatable {}
//func ==(lhs: Object, rhs: Object) -> Bool {
// return lhs.value == rhs.value
//}
let c = Object(value: 3)
let objs = [Object(value: 1), Object(value: 2), Object(value: 3)]
let index = objs.indexOf(c)
let index2 = objs.indexOf { $0 == c }
print(index) // Optional(2)
print(index2) // Optional(2)
The second indexOf method that you are using allows you to define a closure to indicate object equality (like you have already done). The first one according to the documentation:
Returns the first index where value appears in self or nil if value is not found.
Where value is the object you are passing as the parameter. Without seeing the implementation of this version I can only assume they compare if the two objects are equal in every way (all properties match). So in your case, if you only consider the objects equal if the somePropertys match, then you should be using the closure method with your overloaded operator.
static func == (left: MyClass, right: MyClass) -> Bool {
return left.attribute1 == right.attribute1
}
override func isEqual(_ object: Any?) -> Bool {
guard let right = object as? MyClass else {
return false
}
// call the == from above
// why couldn't apple do this ???
//
return self == right
}
Related
I have an old simple ValueTransformer. Written just following the conventional pattern to support a GUI interface to edit a specific encoded file format. I recently has cause to regenerate with current compiler and it is complaining bitterly about converting Bool.self to AnyClass. Yeah, OK, I understand that Bool is no longer a Class (if it ever was) it is a frozen Struct. So the question is that of, is there a Swift way of continuing to use this ValueTransformer for a struct rather than a class?
I can see an ugly solution of wrapping a Bool in a Class and using that but is poor design at best, but if needs must....
Am I missing something obvious in the ever changing world ?
The complaint from the the compiler is on the single line in transformedValueClass
return Bool.self as! AnyClass
Cast from 'Bool.Type' to unrelated type 'AnyClass' (aka 'AnyObject.Type') always fails
class StringToBoolTransformer : ValueTransformer
{
var boolValue : Bool?
override func transformedValue(_ value: Any?) -> Any? {
if let stringRep = value as? String
{
if stringRep.count == 1 {
boolValue = (stringRep == "1" ? true :(stringRep == "0" ? false: nil))
}
}
return boolValue
}
override func reverseTransformedValue(_ value: Any?) -> Any? {
var boolAsString : String?
if let boolValue = value as? Bool {
boolAsString = boolValue ? "1" : "0"
}
return boolAsString
}
override class func transformedValueClass() -> AnyClass
{
return Bool.self as! AnyClass
}
override class func allowsReverseTransformation() -> Bool {
return true
}
}
(NS)ValueTransformer relies on Objective-C and the corresponding class of Bool is NSNumber.
I made the code a bit more contemporary 😉
class StringToBoolTransformer : ValueTransformer
{
override func transformedValue(_ value: Any?) -> Any? {
guard let stringRep = value as? String,
stringRep.count == 1,
["0","1"].contains(stringRep) else { return nil }
let boolValue = stringRep == "1"
return NSNumber(booleanLiteral: boolValue)
}
override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let number = value as? NSNumber else { return nil }
return number.boolValue ? "1" : "0"
}
override class func transformedValueClass() -> AnyClass
{
return NSNumber.self
}
override class func allowsReverseTransformation() -> Bool {
return true
}
}
In Swift Bool is a struct, NOT a class. So you can never cast it to AnyClass.
What you can try as a workaround is use NSNumber class as a storage for bool via NSNumber.init(value: Bool) and then return NSNumber.self from your implementation.
I have a class where I have an extension to check equality but on test, the equality crashes.
here's my code
extension LPItemAction {
public override func isEqual(_ other: Any?) -> Bool {
if (other as? LPItemAction) == self {
return true
} else if !(other is LPItemAction) {
return false
} else {
let otherAction = other as? LPItemAction
return hash == otherAction?.hash
}
}
}
and my test case is like this
func testIsEqualSelf() {
// Given
let action = LPItemAction()
action.type = .account
// When
let equal = action.isEqual(action)
// Then
XCTAssertTrue(equal)
}
I got a crash with error Thread 1: EXC_BAD_ACCESS (code=2, address=0x16e747fc0)
Since this is obviously a NSObject, you are probably right to override isEqual. There are some rules though.
You cannot use ==. This operator invokes the Equality protocol, which, on NSObject is implemented using isEqual, therefore you end up with infinite recursion.
Another thing is that using hash to compare equality is just wrong.
// test type first
guard let otherAction = other as? LPItemAction else {
return false
}
// test reference equality
if self === otherAction {
return true
}
// test equality of all properties
return type === otherAction.type
When subclassing NSObject in Swift, should you override hash or implement Hashable?
Also, should you override isEqual: or implement the == operator?
NSObject already conforms to the Hashable protocol:
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
I could not find an official reference, but it seems that hashValue
calls the hash method from NSObjectProtocol, and == calls the
isEqual: method (from the same protocol). See update at the
end of the answer!
For NSObject subclasses, the correct way seems to be
to override hash and isEqual:, and here is an experiment which
demonstrates that:
1. Override hashValue and ==
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
}
Now create two different instances of the class which are considered
"equal" and put them into a set:
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
As you can see, both NSSet and Set treat the objects as different.
This is not the desired result. Arrays have unexpected results as well:
let nsArrayA = NSArray(object: a1)
let swArrayA = [a1]
print(nsArrayA.indexOfObject(a2)) // 9223372036854775807 == NSNotFound
print(swArrayA.indexOf(a2)) // nil
Setting breakpoints or adding debug output reveals that the overridden
== operator is never called. I don't know if this is a bug or
intended behavior.
2. Override hash and 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
}
}
}
For Swift 3, the definition of isEqual: changed to
override func isEqual(_ object: Any?) -> Bool { ... }
Now all results are as expected:
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)
Update: The behavior is documented in the book "Using Swift with Cocoa and Objective-C", under "Interacting with Objective-C API":
The default implementation of the == operator invokes the isEqual: method, and the default implementation of the === operator checks pointer equality. You should not override the equality or identity operators for types imported from Objective-C.
The base implementation of the isEqual: provided by the NSObject class is equivalent to an identity check by pointer equality. You can override isEqual: in a subclass to have Swift and Objective-C APIs determine equality based on the contents of objects rather than their identities.
The book is available in the Apple Book app.
It was also documented on Apple's website but was removed, and is still visible on the WebArchive snapshot of the page.
For NSObject it is best to override hash and isEqual. It already conforms to Hashable and Equatable and has synthesized conformances for that which in turn invoke hash and isEqual. So since it is an NSObject, do it the ObjC way and override the values that also affect the ObjC hash value and equality.
class Identity: NSObject {
let name: String
let email: String
init(name: String, email: String) {
self.name = name
self.email = email
}
override var hash: Int {
var hasher = Hasher()
hasher.combine(name)
hasher.combine(email)
return hasher.finalize()
}
override func isEqual(_ object: Any?) -> Bool {
guard let other = object as? Identity else {
return false
}
return name == other.name && email == other.email
}
}
"Also, should you override isEqual: or implement ==?"
You could do both. And in case you'd make the implementations
behave differently you'd add flare to the life of the users of your code too. Been there, done that. It's fun.
Implement Hashable, which also requires you to implement the == operator for your type. These are used for a lot of useful stuff in the Swift standard library like the indexOf function which only works on collections of a type that implements Equatable, or the Set<T> type which only works with types that implement Hashable.
Why do I have to add != to make the comparison correct?
import UIKit
class Person: NSObject {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
extension Person {
static func ==(lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name && lhs.age == rhs.age
}
static func !=(lhs: Person, rhs: Person) -> Bool {
return !(lhs == rhs)
}
}
let first = Person(name: "John", age: 26)
let second = Person(name: "John", age: 26)
/**
* return false (which is correct) when we implement != function. But,
* it will return true if we don't implement the != function.
*/
first != second
Update:
So I got why I had to add != function to make it work. it's because the class inherit the NSObject which uses isEqual method behind the scene. But why does adding != function make it work? Any explanation here?
NSObject conforms to Equatable but uses its own isEqual method and in terms of isEqual both instances are not equal. NSObject calls == only if your form of != is implemented, which contains ==.
If you delete NSObject (and add Equatable) the implementation of == works as expected.
The recommended way for NSObject is to override isEqual with a custom implementation and omit == (and !=).
Sorry, this is not a direct answer to your question.
As Alexander commented, Swift Standard Library has this default implementation of !=:
Equatable.swift
#_transparent
public static func != (lhs: Self, rhs: Self) -> Bool {
return !(lhs == rhs)
}
I cannot explain this behavior well, but the == operator in the default implementation above is solved to the default == operator for NSObject, as NSObject (and also its descendants) is already Equatable and has an == operator to conform to Equatable. So, even if the explicit representation is exactly the same as your != definition, the == operators are solved to different implementations.
A general guidline to define your own equality to an NSObject-descendant class:
Make == and isEqual(_:) consistent
You may store your class's instance inside NSArray or NSDictionary (in many cases implicitly). Inside their methods, isEqual(_:) is used when equality check is needed, not the == operator.
So, just defining the == operator without giving a consistent override to isEqual(_:), such methods will generate unexpected result.
To make consistent == and isEqual(_:),
just override only isEqual(_:) and do not define == and != explicitly.
The default implementation of == for NSObject (and also !=) uses isEqual(_:).
class Person: NSObject {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
override func isEqual(_ object: Any?) -> Bool {
if let other = object as? Person {
return self.name == other.name && self.age == other.age
}
return false
}
}
(See ONE MORE THING at the bottom.)
ADDITION
Similar behavior can be found on non-NSObject classes.
class BaseClass {
var a: Int
init(a: Int) {
self.a = a
}
}
extension BaseClass: Equatable {
static func == (lhs: BaseClass, rhs: BaseClass) -> Bool {
print("`==` of BaseClass")
return lhs.a == rhs.a
}
}
let b1 = BaseClass(a: 0)
let b2 = BaseClass(a: 0)
print(b1 != b2) //->`==` of BaseClass, false ### as expected
class DerivedClass: BaseClass {
var b: Int
init(a: Int, b: Int) {
self.b = b
super.init(a: a)
}
}
extension DerivedClass {
static func == (lhs: DerivedClass, rhs: DerivedClass) -> Bool {
print("`==` of DerivedClass")
return lhs.a == rhs.a && lhs.b == rhs.b
}
}
let d1 = DerivedClass(a: 0, b: 1)
let d2 = DerivedClass(a: 0, b: 2)
print(d1 != d2) //->`==` of BaseClass, false ### `==` of DerivedClass and true expected
Seems we need extra care when overriding == for already Equatable classes.
ONE MORE THING
(Thanks for Hamish.)
You know you need to implement == and hashValue consistently when creating a type conforming to Hashable. NSObject is declared as Hashable, and its hashValue needs to be consistent with hash. So, when you override isEqual(_:) in your NSObject-descendent, you also should override hash consistent with your overridden isEqual(_:).
So, your Person class should be something like this:
class Person: NSObject {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
override func isEqual(_ object: Any?) -> Bool {
if let other = object as? Person {
return self.name == other.name && self.age == other.age
}
return false
}
override var hash: Int {
//### This is just an example, but not too bad in practical use cases.
return name.hashValue ^ age.hashValue
}
}
What you are doing is wrong. You should not implement == or !=. An NSObject subclass automatically implements == as isEqual:. You are disrupting that. You should implement isEqual: and that's all.
When subclassing NSObject in Swift, should you override hash or implement Hashable?
Also, should you override isEqual: or implement the == operator?
NSObject already conforms to the Hashable protocol:
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
I could not find an official reference, but it seems that hashValue
calls the hash method from NSObjectProtocol, and == calls the
isEqual: method (from the same protocol). See update at the
end of the answer!
For NSObject subclasses, the correct way seems to be
to override hash and isEqual:, and here is an experiment which
demonstrates that:
1. Override hashValue and ==
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
}
Now create two different instances of the class which are considered
"equal" and put them into a set:
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
As you can see, both NSSet and Set treat the objects as different.
This is not the desired result. Arrays have unexpected results as well:
let nsArrayA = NSArray(object: a1)
let swArrayA = [a1]
print(nsArrayA.indexOfObject(a2)) // 9223372036854775807 == NSNotFound
print(swArrayA.indexOf(a2)) // nil
Setting breakpoints or adding debug output reveals that the overridden
== operator is never called. I don't know if this is a bug or
intended behavior.
2. Override hash and 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
}
}
}
For Swift 3, the definition of isEqual: changed to
override func isEqual(_ object: Any?) -> Bool { ... }
Now all results are as expected:
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)
Update: The behavior is documented in the book "Using Swift with Cocoa and Objective-C", under "Interacting with Objective-C API":
The default implementation of the == operator invokes the isEqual: method, and the default implementation of the === operator checks pointer equality. You should not override the equality or identity operators for types imported from Objective-C.
The base implementation of the isEqual: provided by the NSObject class is equivalent to an identity check by pointer equality. You can override isEqual: in a subclass to have Swift and Objective-C APIs determine equality based on the contents of objects rather than their identities.
The book is available in the Apple Book app.
It was also documented on Apple's website but was removed, and is still visible on the WebArchive snapshot of the page.
For NSObject it is best to override hash and isEqual. It already conforms to Hashable and Equatable and has synthesized conformances for that which in turn invoke hash and isEqual. So since it is an NSObject, do it the ObjC way and override the values that also affect the ObjC hash value and equality.
class Identity: NSObject {
let name: String
let email: String
init(name: String, email: String) {
self.name = name
self.email = email
}
override var hash: Int {
var hasher = Hasher()
hasher.combine(name)
hasher.combine(email)
return hasher.finalize()
}
override func isEqual(_ object: Any?) -> Bool {
guard let other = object as? Identity else {
return false
}
return name == other.name && email == other.email
}
}
"Also, should you override isEqual: or implement ==?"
You could do both. And in case you'd make the implementations
behave differently you'd add flare to the life of the users of your code too. Been there, done that. It's fun.
Implement Hashable, which also requires you to implement the == operator for your type. These are used for a lot of useful stuff in the Swift standard library like the indexOf function which only works on collections of a type that implements Equatable, or the Set<T> type which only works with types that implement Hashable.