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")
}
Related
class TEST1 : NSObject {
var name : String?
}
class TEST2 : NSObject {
var name : String?
}
func compareObjects<T>(array1: [NSObject], array2: [NSObject], type:T.Type) {
for objectA in array1 {
let x = objectA as! T
for objectB in array2{
let y = objectA as! T
if x.name == y.name {
print("found a match")
}
}
}
}
I don't think this is allowed.
But if you all know a way to make it work it would be much appreciated. it'll save me a lot of duplicate code.
Why you do that? You can simply implement Hashable protocol and override == operator to compare two objects. Then you can simply write: x == y without any loops.
I am learning swift. I would like to use a custom class to be loopable [able to a for...in loop] like Array. Below is the given sample code that so far, I have tried. The class in question is "GuestManager" which is holding a private collection of guests [objects of class Guest]
import Foundation
class Guest{
var guestId: String
var guestName: String
init(gId: String, name: String){
self.guestId = gId
self.guestName = name
}
}
class GuestManager: GeneratorType, SequenceType{
private var guests = [Guest]?()
private var nextIndex: Int
init(guests: [Guest]){
self.guests = guests
self.nextIndex = 0
}
func next() -> Guest? {
if self.nextIndex > (self.guests?.count)! - 1 {
self.nextIndex = 0
return nil
}
let currentGuest = self.guests![self.nextIndex]
self.nextIndex += 1
return currentGuest
}
subscript(aGuestId gID: String) -> Guest{
return (self.guests?.filter({$0.guestId == gID}).first)!
}
}
I do not want to create separate classes that are conforming to GeneratorType & SequenceType protocols. Instead I have created a single class that is conforming to both protocols.
Below are some of my questions:
I would like to know if this a correct way to have a custom collection type ?
Can we use subscript as a way to perform a search based on a property for example "subscript(aGuestId gID: String)" in the sample code above ?
It is clear from the code for next() function implementation in above sample code that is resetting the "nextIndex" when the iteration reached at the end. How one will handle the situation wherein we use a break statement inside the for...in loop as below:
for aGuest in guestManager{//guestManager an instance of GuestManager class instantiated with several guest objects
print(aGuest.guestName)
}
for aG in guestManager{
print(aG.guestId)
break
}
In the 2nd for loop the code break out after getting the first Element [Guest object in this case]. The subsequent for loop will start at index 1 in the collection and not at 0. Is there anyway to handle this break situation so that for each subsequent for looping the index is always set to 0?
Thanks
Edit: It seems the "nextIndex" reset issue can be fixed with below code [added inside GuestManager class] for generate() method implementation
func generate() -> Self {
self.nextIndex = 0
return self
}
You should not store the nextIndex inside the class. You can use a local variable in the generate method and then let that variable be captured by the closure you pass to the generator you create in that method. That’s all you need to adopt SequenceType:
class GuestManager: SequenceType{
private var guests: [Guest]
init(guests: [Guest]) {
self.guests = guests
}
func generate() -> AnyGenerator<Guest> {
var nextIndex = 0
return AnyGenerator {
guard nextIndex < self.guests.endIndex else {
return nil
}
let next = self.guests[nextIndex]
nextIndex += 1
return next
}
}
}
For subscripting, you should adopt Indexable. Actually, the easiest way to fulfill all your requirements is to pass as much of the logic for SequenceType, Indexable, and eventually (if you want to support it) CollectionType, to your array, which already has these capabilities. I would write it like this:
class GuestManager {
private var guests: [Guest]
init(guests: [Guest]){
self.guests = guests
}
}
extension GuestManager: SequenceType {
typealias Generator = IndexingGenerator<GuestManager>
func generate() -> Generator {
return IndexingGenerator(self)
}
}
extension GuestManager: Indexable {
var startIndex: Int {
return guests.startIndex
}
var endIndex: Int {
return guests.endIndex
}
subscript(position: Int) -> Guest {
return guests[position]
}
}
Some more observations:
Your guests property should not be an optional. It makes the code more complicated, with no benefits. I changed it accordingly in my code.
Your Guest class should probably be a value type (a struct). GuestManager is also a good candidate for a struct unless you require the reference semantics of a class (all collection types in the standard library are structs).
I think the subscripting approach you're trying here is kind of convoluted. Personally, I would use a function to do this for the sake of clarity.
guestManager[aGuestId: guestId]
guestManager.guestWithID(guestId)
So stylistically I would probably land on something like this
import Foundation
class Guest{
var guestId: String
var guestName: String
init(guestId: String, guestName: String){
self.guestId = guestId
self.guestName = guestName
}
}
class GuestManager: GeneratorType, SequenceType{
private var guests: [Guest]
private var nextIndex = 0
init(guests: [Guest]){
self.guests = guests
}
func next() -> Guest? {
guard guests.count < nextIndex else {
nextIndex = 0
return nil
}
let currentGuest = guests[nextIndex]
nextIndex += 1
return currentGuest
}
func guestWithID(id: String) -> Guest? {
return guests.filter{$0.guestId == id}.first ?? nil
}
}
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
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!
XCode 6, Beta 5
I have a unit test like this:
func testMyObjectsEqual()
{
//....
XCTAssertEqual(myObject, myOtherObject, "\(myObject) and \(myOtherObject) should be equal")
}
XCTAssertEqualObjects is no longer available in Swift since the language makes no distinction between scalars and objects.
So we have to use XCTAssertEqual which leads to the following error:
"Type MyObject does not conform to protocol Equatable"
The only workaround I have found is to inherit (MyObject) from NSObject so that I can do the following:
XCTAssert(myObject == myOtherObject, "\(myObject) and \(myOtherObject) should be equal")
So my question is: Is there a way (as of beta 5) to use XCTAssertEqual for custom types without having to rely on NSObject or littering all custom types with "==" overloads ?
If you want to compare references in Swift, you can use === operator. This is what happens when you subclass from NSObject (when you are comparing objects in Objective-C using XCTAssertEqual, you are comparing references).
But is this really what you want?
class MyObject {
var name: String
init(name: String) {
self.name = name
}
}
func testMyObjectsEqual() {
let myObject = MyObject(name: "obj1")
let myOtherObject = MyObject(name: "obj1")
let otherReferenceToMyFirstObject = myObject
XCTAssert(myObject === myOtherObject) // fails
XCTAssert(myObject === otherReferenceToMyFirstObject) // passes
}
I guess that when you are comparing custom objects, you probably should make them conform to the Equatable protocol and specify, when your custom objects are equal (in the following case the objects are equal when they have the same name):
class MyObject: Equatable {
var name: String
init(name: String) {
self.name = name
}
}
func ==(lhs: MyObject, rhs: MyObject) -> Bool {
return lhs.name == rhs.name
}
func testMyObjectsEqual()
{
let myObject = MyObject(name: "obj1")
let myOtherObject = MyObject(name: "obj1")
XCTAssertEqual(myObject, myOtherObject) // passes
}
Since Xcode 12.5 there is XCTAssertIdentical