Equatable implementation doesn't seem working with generics - ios

I am still fighting with Swift generics. Today I discovered that my implementation of Equatable protocol doesn't work, if it's called from generic class.
My model class:
func ==(lhs: Tracking, rhs: Tracking) -> Bool {
// This method never executes if called from BaseCache
return lhs.id == rhs.id
}
class Tracking: NSObject, Equatable, Printable {
var id: String?
.....
}
Class, which uses generic type:
class BaseCache<T: NSObject where T: Equatable, T: Printable> {
.....
func removeEntities(entities: [T]) {
var indexesToRemove = [Int]()
for i in 0...allEntities.count - 1 {
let item = allEntities[i]
for entity in entities {
println("equal: \(entity == item)")
// FOR SOME REASONS THE STATEMENT BELOW IS ALWAYS FALSE
if entity == item {
indexesToRemove.append(i)
break
}
}
}
for index in indexesToRemove {
allEntities.removeAtIndex(index)
}
didRemoveEntities()
}
}
and it's subclass:
class TrackingCache<T: Tracking>: BaseCache<Tracking> {
}
When I call removeEntities method of TrackingCache instance, I always get equal: false in the output, even if ids are the same.
But if I move the method to TrackingCache class directly, it seems working fine!
Any ideas why this is happening and how to fix this?

Beware: since == is not a member function, it will not give you dynamic dispatch by default, including if you use it alongside a generic placeholder.
Consider the following code:
class C: NSObject, Equatable {
let id: Int
init(_ id: Int) { self.id = id }
}
// define equality as IDs are equal
func ==(lhs: C, rhs: C) -> Bool {
return lhs.id == rhs.id
}
// create two objects with the same ID
let c1 = C(1)
let c2 = C(1)
// true, as expected
c1 == c2
OK now create two variables of type NSObject, and assign the same values to them:
let o1: NSObject = c1
let o2: NSObject = c2
// this will be false
o1 == o2
Why? Because you are invoking the function func ==(lhs: NSObject, rhs: NSObject) -> Bool, not func ==(lhs: C, rhs: C) -> Bool. Which overloaded function to choose is not determined dynamically at runtime based on what o1 and o2 refer to. It is determined by Swift at compile time, based on the types of o1 and o2, which in this case is NSObject.
NSObject == is implemented differently to your equals – it calls lhs.isEqual(rhs), which falls back if not overridden to checking reference equality (i.e. are the two references pointing at the same object). They aren’t, so they aren’t equal.
Why does this happen with BaseCache but not with TrackingCache? Because BaseCache is defined as constraining only to NSObject, so T only has the capabilities of an NSObject – similar to when you assigned c1 to a variable of type NSObject, the NSObject version of == will be called.
TrackingCache on the other hand guarantees T will be at least a Tracking object, so the version of == for tracking is used. Swift will pick the more "specific" of all possible overloads – Tracking is more specific than it's base class, NSObject.
Here’s a simpler example, just with generic functions:
func f<T: NSObject>(lhs: T, rhs: T) -> Bool {
return lhs == rhs
}
func g<T: C>(lhs: T, rhs: T) -> Bool {
return lhs == rhs
}
f(c1, c2) // false
g(c1, c2) // true
If you want to fix this, you can override isEqual:
class C: NSObject, Equatable {
...
override func isEqual(object: AnyObject?) -> Bool {
return (object as? C)?.id == id
}
}
// this is now true:
o1 == o2
// as is this:
f(c1, c2)
This technique (having == call a dynamically-dispatched class method) is also a way to implement this behaviour for your non-NSObject classes. Structs, of course, don’t have this issue since they don’t support inheritance – score 1 for structs!

Related

Swift: How to compare two Dictionary<String, Any> structs?

I'm using the Static framework written in Swift that uses typealias Context = [String: Any] for storing additional information for a row.
Now, I need to compare if two Context structs are equal in an extension Row:
public func ==(lhs: Row, rhs: Row) -> Bool {
// TODO: image, ...
return lhs.text == rhs.text
&& lhs.detailText == rhs.detailText
&& lhs.accessory == rhs.accessory
&& lhs.context == rhs.context
}
So I implement the == operator for Row.Context:
public func ==(lhs: Row.Context, rhs: Row.Context) -> Bool {
return Array(lhs.keys) == Array(rhs.keys)
// TODO: && Array(lhs.values) == Array(rhs.values)
}
but now, in order to lhs.context == rhs.context I need to extend the Row.Context type to conform to Equatable:
extension Row.Context: Equatable {}
Which is exactly where I'm stuck with the following error:
"Constrained extension must be declared on the unspecialized generic
type 'Dictionary' with constraints specified by a 'where' clause"
Ignoring the implementation of func == for Row.Context for now, how to make Row.Context conform to Equatable?

Cannot invoke 'indexOf' with an argument list of type '(ChecklistItem)'

When I am writing code for finding an item from the array with the use of indexOf it shows me the above stated error.
Here is my code:-
func addItemViewController(controller: AddItemViewController, didFinishEditingItem item: ChecklistItem)
{
if let index = items.indexOf(item)
{
let indexPath = NSIndexPath(forRow: index, inSection: 0)
if let cell = tableView.cellForRowAtIndexPath(indexPath)
{
configureTextForCell(cell, withChecklistItem: item)
}
}
In order to use indexOf the ChecklistItem must adopt Equatable protocol. Only by adopting this protocol the list can compare an item with other items to find the desired index
indexOf can only be applied to Collections of Equatable types, your ChecklistItem doesn't conform to Equatable protocol (have an == operator).
To be able to use indexOf add this to the file containing ChecklistItem class in the global scope :
func ==(lhs: ChecklistItem, rhs: ChecklistItem) -> Bool {
return lhs === rhs
}
Swift3:
public static func ==(lhs: Place, rhs: Place) -> Bool {
return lhs === rhs
}
Please note it will make comparison by comparing instances addresses in memory. You may want to check equality by comparing members of the class instead.
In Swift 4 and Swift 3, update your Data Model to conform to "Equatable" protocol, and implement the lhs=rhs method , only then you can use ".index(of:...)", because you are comparing your custom object
Eg:
class Photo : Equatable{
var imageURL: URL?
init(imageURL: URL){
self.imageURL = imageURL
}
static func == (lhs: Photo, rhs: Photo) -> Bool{
return lhs.imageURL == rhs.imageURL
}
}
Usage:
let index = self.photos.index(of: aPhoto)
I realize this question already has an accepted answer, but I found another case that will cause this error so it might help someone else. I'm using Swift 3.
If you create a collection and allow the type to be inferred you may also see this error.
Example:
// UITextfield conforms to the 'Equatable' protocol, but if you create an
// array of UITextfields and leave the explicit type off you will
// also see this error when trying to find the index as below
let fields = [
tf_username,
tf_email,
tf_firstname,
tf_lastname,
tf_password,
tf_password2
]
// you will see the error here
let index = fields.index(of: textField)
// to fix this issue update your array declaration with an explicit type
let fields:[UITextField] = [
// fields here
]
The possible reason is you didn't tell the ChecklistItem type that it is equatable, maybe you forgot to mention ChecklistItem class is inherited from NSObject.
import Foundation
class ChecklistItem: NSObject {
var text = ""
var checked = false
func toggleChecked() {
checked = !checked
}
}
NSObject adopts or conforms to the equatable protocol

RLMObjects not recognised as the same in Realm Cocoa

I have a tableView containing RLMObjects and want to search for the row containing a specific RLMObject.
After casting the RLMResult object to its original type it is not the same as its original object:
// ... adding a todoA with !isCompleted to defaultRealm()
var firstItem = Todo.objectsWhere("isCompleted == false")[0] as! ToDo
if firstItem == todoA {
// todoA is != firstItem even-though they should be the same object
}
How can I compare two RLMObjects without having to implement the primaryKey allocation?
RLMObject does not conform to Swift's Equatable protocol, allowing == and != comparisons. Depending on the equality semantics you want for your objects, you can use the following extension on RLMObject:
extension RLMObject: Equatable {}
func == <T: RLMObject>(lhs: T, rhs: T) -> Bool {
return lhs.isEqualToObject(rhs)
}

Can't make Swift use my Generic == Operator

My app contains many Core Data NSManagedObject subclasses, all of which have an NSDate which I call their recordID. When comparing two objects, I want to use this data to determine if they are the same. Now since there are many subclasses, I created a protocol to show that they all implement a recordID:
protocol HasID
{
var recordID: NSDate {get}
}
Simple, right? Now I have implemented the == operator as follows:
func == <T: HasID>(left: T, right: T) -> Bool
{
return left.recordID == right.recordID ? true : false
}
Problem- Swift doesn't use my beautiful == operator and instead compares with some generic crap as follows
func ==(lhs: NSObject, rhs: NSObject) -> Bool
Now if I implement == for each individual subclass as follows
func == (left: Pilot, right: Pilot) -> Bool
{
return left.recordID == right.recordID ? true : false
}
Then it uses my operator and works. (I've also got a == operator implemented for NSDate which is why the above code is fine.)
Any idea how I can get my generic == operator to be used rather than the NSObject one?
Putting together previous comments and proposal, it seems it is due to NSManagedObject.
Put the following code in a playground:
import UIKit
import CoreData
protocol HasID
{
var recordID: NSDate {get}
}
func == <T: HasID>(left: T, right: T) -> Bool
{
println("==")
return left.recordID.isEqualToDate(right.recordID)
}
func ~= <T: NSManagedObject where T: HasID>(left: T, right: T) -> Bool
{
println("~=")
return left.recordID.isEqualToDate(right.recordID)
}
class Managed: NSManagedObject, HasID {
let recordID: NSDate = {
let components = NSDateComponents()
components.day = 31
components.month = 3
components.year = 2015
let gregorian = NSCalendar(calendarIdentifier: NSGregorianCalendar)!
return gregorian.dateFromComponents(components)!
}()
}
let foo = Managed()
let bar = Managed()
foo == bar
foo ~= bar
switch foo {
case bar:
println("Equal")
default:
println("Not Equal")
}
As you will see the overloaded == is never called, but ~= is.
Thus I suggest not going too far forcing NSManagedObject to use an overloaded == and use ~= instead. As you can see, you will get it to work in switch statements as well (in fact it is the pattern match operator).
If you play removing NSManagedObject from my example, you will see that for an ordinary class, your solution would work.
My opinion is that == operator for NSManagedObject is used to compare between managed object ids, and not pointers in memory. The same managed object could have different pointers in memory when it is present in different managed object context. That's why I wouldn't try to overload it. It helps keep track of the same managed object in different context.
You can declare an infix operator:
// I've modified your protocol just for my example
protocol HasID
{
var recordID: Int {get}
}
infix operator <*> {
associativity right
}
func <*> (left: HasID, right: HasID) -> Bool
{
return left.recordID == right.recordID ? true : false
}
// example:
class Fake: HasID {
var recordID = 1
}
class Bull: HasID {
var recordID = 1
}
let fake = Fake()
let bull = Bull()
if fake <*> bull {
println("is equal")
}

Cannot invoke 'contains' with an argument list of Type '([Foo], Foo)'

I'm attempting to use the contains function in swift to see if my objects is in a typed array but I'm getting:
Cannot invoke 'contains' with an argument list of Type '([Foo], Foo)'
class Foo {
}
let foo = Foo()
let foos = [Foo(), Foo()]
contains(foos, foo)
Why is this happening?
Update #1
I've implemented the == function but I still get the same error. Am I doing this improperly?
class Foo {}
func ==(lhs: Foo, rhs: Foo) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}
let foo = Foo()
let foos = [Foo(), Foo()]
contains(foos, foo)
Classes don't automatically inherit any equality logic from the base classs, so you need to be explicit and have Foo to conform to the Equatable protocol.
Actually, the only sensible equality the compiler could derive from that class declaration is the identity, and you probably don't want that.
Please note that
class Foo {}
is not the same as
class Foo : NSObject { }
By inheriting from NSObject you also inherit the default implementation of isEqual, which provides object identity equality.
Concerning your last update, you're only missing the Equatable protocol in the class definition. The following compiles just fine
class Foo : Equatable {}
func ==(lhs: Foo, rhs: Foo) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
// or simply
// return lhs === rhs
}
let foo = Foo()
let foos = [Foo(), Foo()]
contains(foos, foo)
Or simply inherit from NSObject, which already provides identity equality
class Foo : NSObject {}
let foo = Foo()
let foos = [Foo(), Foo()]
contains(foos, foo)
The only function signature for contains is:
func contains<S : SequenceType where S.Generator.Element : Equatable>(seq: S, x: S.Generator.Element) -> Bool
Foo is not Equatable, so it doesn't match this signature.
Also note that there is always a way to check for an instance of a class in an array, even if you haven't implemented the equatable protocol or inherited from NSObject. You can use reduce to do it like this:
class MyClass { }
let myClass1 = MyClass()
let myArray = [myClass1]
let lookForMyClass1 = myArray.reduce(false) { $0 || $1 === myClass1 }
println(lookForMyClass1) // Outputs: true
And you can generalize that to look for any class object in any SequenceType by overloading contains with something like this:
func contains<T: AnyObject, S: SequenceType where T == S.Generator.Element>(#haystack: S, #needle: T) -> Bool {
return reduce(haystack, false) { $0 || $1 === needle }
}
Now you can call contains like this:
let searchForMyClass1 = contains(haystack: myArray, needle: myClass1)
println(searchForMyClass1) // Output: true

Resources