How to override hash and isEqual for NSManagedObjects? - ios

We have a bunch of NSManagedObjects of various types.
Some of them have members that are NSSet's of other NSManagedObjects.
The problem is that I really need to override the hash and isEquals methods of the objects that are IN the set - but they are NSManagedObjects.
I'm having problems with getting multiple identical objects in the set.
As far as I can tell, since hash defaults to the object address - all objects are different. So I need to override hash and isEquals - but can't see any way to do it.
What we have is a bunch of stuff in the System, and more comes in via XML - sometimes repeats of the existing objects. When they are the same, I don't want dups added to the set.

As mentioned above by Wain, NSManagedObject documentation states that you must not override hash or isEqual:. So this means a stock NSSet does not do what you need.
Some of your options are:
Enumerate the NSSet contents to identify and remove duplicates
Write a factory method for your NSManagedObjects that will return the same object when given the same inputs
Fix the XML to not include duplicated objects
Unique the objects coming from the XML before they become NSManagedObjects
Modify the input XML to include a unique identifier that you can track, assuming the duplicated objects are exact duplicates
Implement your own NSSet-like collection class that performs a different uniquing test than hash and isEqual:

Related

CoreData "forgets" order of NSOrderedSet relationship

I have a NSOrderedSet relaitonship on an entity. The order of the objects is correct until I save, quit and relaunch the app. Then, when the entity is fetched, For some instances of the entity, the order of this relaitonship is different from what it was prior to the relaunch. It's as if the NSOrderedSet completely forgot the order.
Inspecting the model file shows that this property is indeed ordered:
<relationship name="videoSegments" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="VideoSegment" inverseName="parentProject" inverseEntity="VideoSegment" syncable="YES"/>
I know about the autogenerated accessor code issue: Exception thrown in NSOrderedSet generated accessors however this is a different problem although it may be related somehow.
It doesn't forget anything, you should sort it. There is a difference between ordered and sorted.
Check NSMutableOrderedSet
If you need to return the data in a specific order, you need to add an attribute which defines the sort order, such as an index from 0..n, and sort the results on that.
CoreData doesn't pay any attention to the order in which you added the data

NSMutableDictionary contents inconsistent with output of allValues

So long story short, there's a discrepancy between the output of a NSMutableDictionary's contents and the result of calling allValues on the same object. Below is some debugger output after inspecting the object which demonstrates my problem: (made generic of course)
(lldb) po self.someDict.allKeys
<__NSArrayI 0xa5a2e00>(
<SomeObject: 0xa5a2dc0>,
<SomeObject: 0xa5a2de0>
)
(lldb) po self.someDict.allValues
<__NSArrayI 0xa895ca0>(
0.5,
0.5
)
(lldb) po self.someDict
{
"<SomeObject: 0xa5a2dc0>" = (null);
"<SomeObject: 0xa5a2de0>" = (null);
}
So as we can see, the actual output of the NSMutableDictionary contains null values for both its entries, but the contents of .allValues contains the proper data. These three outputs were taken at the same time in execution.
I'm not sure why this is happening, but I think it may have something to do with the fact that I'm encoding/decoding the object which this dictionary is a property of using CoreData. I believe I'm doing this properly:
[aCoder encodeObject:self.someDict forKey:#"someDict"];
and to decode
self.someDict = [aDecoder decodeObjectForKey:#"someDict"];
The weird thing is that if I inspect the dictionary before it ever gets encoded, it is still in the state described at the beginning of the post, so this is why I'm doubtful the CoreData actions are screwing with the contents.
As always, please don't hesitate to request additional information.
EDIT: The problem was as answered below. I was using a custom class which didn't cooperate with isEqual, so my solution was to change the storage and structure of my logic, which made using a Dictionary unnecessary.
I have not been able to duplicate the problem using NSString as keys and NSNumber as values. I suspect that your custom class does not properly implement hash and/or isEqual. Specifically, the results from those two methods must be consistent, meaning that if isEqual returns true, then the hash values for the two objects must be identical.
You also need to ensure that your class implements NSCopying properly and that a copy is equal to the original.
As a general rule, don't use custom objects for dictionary keys. Just use strings and be done with it.
As user3386109 points out, custom objects must properly implement the -hash and -isEqual methods in order to be used as dictionary keys, and even then, custom objects don't work correctly for dictionary keys for things like key/value coding.

Understanding Transient properties with NSFetchedResultsController

I'm starting to create an application with Core Data, to retrieve a data for sectioned table i want to use NSFetchedResultController, in the example from apple there are two additional properties.
primitiveTimeStamp
primitiveSectionIdentifier
For the case of primitiveSectionIdentifier apple says that
In contrast, with transient properties you specify two attributes and
you have to write code to perform the conversion.
because the sectionidentifier is transient property.
But what about the timeStamp ?this attribute is not a transient, why there is a primitiveTimeStamp property ? and why there is explicit setter for timeStamp ?
- (void)setTimeStamp:(NSDate *)newDate {
// If the time stamp changes, the section identifier become invalid.
[self willChangeValueForKey:#"timeStamp"];
[self setPrimitiveTimeStamp:newDate];
[self didChangeValueForKey:#"timeStamp"];
[self setPrimitiveSectionIdentifier:nil];
}
or maybe it's not a actual setter? where is _timeStamp=newDate?
CoreData generates the accessors for you. It generates "public and primitive get and set accessor methods for modeled properties".
So in this case it has generated:
-(NSDate*)timeStamp;
-(void)setTimeStamp:;
-(NSDate*)primitiveTimeStamp;
-(void)setPrimitiveTimeStamp:;
"why there is a primitiveTimeStamp property ?"
The declaration is merely to suppress compiler warnings. ie. If you removed the declaration of the property you'd find a warning on compilation but the code would still run.
Or alternatively you could use [self setPrimitiveValue:newDate forKey:#"timeStamp"];
"why there is explicit setter for timeStamp ?"
This is required since setting the timeStamp requires the 'sectionIdentifier' to be recalculated. This is achieved by setting it no nil and letting the get accessor recalculate it lazily.
"where is _timeStamp=newDate?"
The equivalent of this is essentially done in the auto generated implementation of setPrimitiveTimeStamp.
A quote from the docs:
By default, Core Data dynamically creates efficient public and primitive get and set accessor methods for modeled properties (attributes and relationships) of managed object classes. This includes the key-value coding mutable proxy methods such as addObject: and removes:, as detailed in the documentation for mutableSetValueForKey:—managed objects are effectively mutable proxies for all their to-many relationships.
Note: If you choose to implement your own accessors, the dynamically-generated methods never replace your own code.
For example, given an entity with an attribute firstName, Core Data automatically generates firstName, setFirstName:, primitiveFirstName, and setPrimitiveFirstName:. Core Data does this even for entities represented by NSManagedObject. To suppress compiler warnings when you invoke these methods, you should use the Objective-C 2.0 declared properties feature, as described in “Declaration.”

How to best check if the contents of two objects are identical?

I have an array of cached objects that I retrieve using NSCoding and NSKeyedUnarchiver. These have have many properties.
I now need to check if the contents of an object I create is identical to any of the cached objects contents in the array.
I of course cannot check if the references to the objects are equal using containsObject, but I can check if their contents are identical. I know how to achieve the end result, but what's best practice in this case? I would want it to take as little time as possible.
And keep in mind that the objects are only identical if all their properties match.
Thank you for your time!
Implement the isEqual: and hash methods on the class. The implementation of isEqual: should compare all properties.
Once you have those two methods properly implemented you can make use of collection methods such as NSArray containsObject: or NSArray indexOfObjects:, etc.

Deleting or removing ManagedObject in CoreData

In the documentation and in the broad literature the generated factory method to delete/remove a subclassed managed object in CoreData for IOS is
(void)removeXXXObject:(NSManagedObject *)value
where XXX is the corresponding relationship or we can use simply removeObject.
In my code I used this:
Data *lastData = [[self sortedPersonDatas] objectAtIndex:0];
[selectedPerson removePersonDatasObject:lastData];
where PersonDatas is a one-to-many relationship to Data managed object from I took the last data (lastData resulted from a sorted array of all data)
But using the first two remove methods and checking the SQL database behind we can find that the actual data is existing just the inverse relationship is null.
To completely delete the data (all attributes and the object) I had to use:
[selectedPerson.managedObjectContext deleteObject:lastData];
The question: which is the better method and is it correct that CoreData leaves the data intact?
removeXXXObject only removes an object from a to-many relationship, but does not delete the object from the store. To do so, you have to indeed use deleteObject - this is the desired behavior. Calling deleteObject will by default also set the corresponding relationships to nil (see https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdRelationships.html#//apple_ref/doc/uid/TP40001857-SW1).

Resources