Core Data. Detect same object? - ios

I have 2 Core Data objects of the same type. I got them via different ways.
At the same time these objects have an NSString field (identifier).
So what is better?
1)compare objects via isEqual:
2)compare objects via == (is it possible in general?)
3)compare objects' fields via isEqualToString:?

Basically, never use == on objects. Always use isEqual:.
If you're using a predicate then you can use LIKE on the identifier.

If you are trying to determine whether or not two objects are the semantically equal and you can describe what equality looks like for that type, then use isEqual. For example, if you have a Person class, you might define equality based on SSN (in the US) or something equally unique.
If you are trying to determine that two objects have something specific in common, but it doesn't necessarily imply that they are equal (for example, using the same Person example, if you want find out if two instances have the same last name), compare the attributes using isEqualToString:. It would not make sense to put this comparison into isEqual for the type.
If you are trying to find out if you have two NSManagedObject instances from your persistent store that are actually the same object fetched via different NSManagedObjectContexts, use the objectID for comparison.

Related

Core Data: best way of checking the uniqueness of an attribute

As far as I know, there is no way of setting an entity's attribute as unique through Core Data, neither programmatically nor in Xcode's editor... I need to make sure that certain managed objects can't be created if there are collisions with the values of the attributes I want to be unique, and I've been reading some posts dealing with that.
I've found a couple of approaches (e.g. Core Data unique attributes):
To use -validateValue:forKey:error:
To write some kind of custom method to check if the attribute's value already exists
What should the most recommendable option be?
Thanks
You're going to need some kind of custom code, whether you put it in validateValue:forKey:error: or in a custom method or somewhere else.
Whether to use the built-in validation method is really a matter of how you prefer to organize your code. I'd prefer to do something like
Check to see if the value is unique.
If so, then insert a new instance.
That's partly because I find the built-in validation scheme to be a pain, but mostly it's because that code will run every time you save changes to an object. If your check is in validateValue:forKey:error:, you'll run it repeatedly, even after you've verified that the value is unique. Then again maybe you need to do that, so the best answer in your case depends on a bigger picture of how your app needs to work.
The simple way to approach validation is by doing a fetch with a predicate identifying the key and value that you need to check. The one change I'd make to the common fetching approach as described in the other answer is that I'd use countForFetchRequest:error: instead of executeFetchRequest:error:. It doesn't sound like you actually need to fetch existing objects during validation, you just need to know whether any exist, so just check that.
Depending on the type of the unique attribute, you may be able to reduce the performance hit that you're going to take by doing this. For example, if it's a string. Checking all existing strings for a match is relatively expensive. On the other hand checking a bunch of existing integers is cheap. In that case you might find it worthwhile to add a numeric property to your entity type that stores a numeric hash of the unique string value. Use the hash only when checking uniqueness. It'll be a hell of a lot faster than looking for matching strings, and NSString even has a handy hash method to calculate the value for you.

Database Design without NULL values and Repeating Data for iOS App

Having figured out most of my data-model for a new iOS app, I'm now stuck with a problem that I've been thinking about for a while.
An 'Experiment' has a name, description and owner. It also has one 'Action' and one 'Event'.
An 'Event' could be different things: Time, Location or Speed.
Depending on what the 'Event' is, it can have a different 'Type'. For example, Time could be one-off, interval, date-range, repeating or random. Location could be area or exact location.
Each 'Type' then has a value that has a data type unique to itself. The Time One-Off could be a date value of 12:15pm and the Location Exact could be a GeoPoint value of (30.0, -20.0).
The Problem
How do I design the data model so that the database is not riddled
with NULL values?
How do I design the data model to be extensible if I add more 'Events'
and 'Types'.
Thoughts
As an Experiment only has one Action and one Event, it would be wrong to separate these two into different tables, however not doing so would cause the Experiment table to be full of NULL values, as I'd have to have columns for Event, Event Type and Event Type Value to compensate for all of the possible data types one could enter for an Event Type Value. (date, int, string, geopoint, etc)
Separating the Event and Event Type into a separate table would probably fix the NULL value issue however I'd be left with repeating data, especially in the case of time as the Event with Type One-Off as 12:00pm, as this would exist in other experiments, not just one. (Unless I create EVERY possibility and populate a separate table with these - how could I easily do this though?)
Maybe I'm over complicating things, maybe I'm missing something so simple that I'm going to kick myself when I see it.
You need to think about your data model in terms of objects not tables. Core data works with object graphs so everything in core data is an object. In Objective-c you work with objects. This is why you don't need a ORM tool. If you think in terms of objects then I think the model below (obviously needs work but you should get the point) makes sense. The advantage of separating your concepts out into objects like this is that you can look at your problem from multiple angles. In other words you can look at it from the Experiment angle or from the Event angle. I suspect you will want to do something with the data such as use your Time object in your code to show on a calendar or set a reminder. Fetch all the events for all experiments of a specific type, etc. By encapsulating these data items in objects in core data, everything is ready for you to leverage, manipulate and modify in your code. It also removes the null value issue you identified. Because you won't be creating objects for null values, only for values that are relevant to your experiment. That being said, you might want to break down the model even further depending upon the specifics of your program. Also, you would not have the repeating data issue you mention if you design this properly. Again, you're not dealing with rows in a table you are dealing with objects. If you create an Event Type object with "one-off 12:00pm", you can assign that Event Type objec,t through its relationship, to as many Event(s) as you wish. You don't create the object again, you simply reference it. When you think of the relationships think "X can be associated with Y". For example, "An Experiment can be associated with only 1 Event", "An Event Type can be associated with many Events", "An Event can be associated with only 1 Event Type". Taking this approach sets you up for extensibility down the road. Imagine you want to add a new Event Type. You simply create a new event entity and associate it to your Event Type entity.
My suggestion is to think about your object model relative to how you anticipate using the objects in your code (and how you anticipate accessing the objects via queries). That should help drive how you construct it (e.g. if you need a time object then make sure you have that in your object model. If you need an alert object then make sure you have that in your object model). Let the model do the work for you and try not to write a lot of code to assemble the equivalent of an object model within objective-c or start creating objects in code and populating them with data from your data store.
(EDIT: Replace the "event" relationship in the diagram under time, location & speed with "event types")

How NSSet make sure the uniqueness of it objects?

I want to know how NSSet make sure that it has only unique objects? When we try to add duplicate value, then on which criteria it decides that it has already this value? I want to know the underlying uniqueness criteria.
NSSet uses your object's implementation of isEqual: method to decide object equality. It also uses hash to make the lookup much faster.
-(BOOL)isEqual:(id)other;
-(NSUInteger)hash;
When two objects are equal, their hash methods must return the same value (objects with the same hash, however, may not necessarily be equal to each other).
When your hash and isEqual: are implemented properly, NSSet can decide the equality by checking only a handful of objects whose hash "collides" with the hash of the object you are adding to the set.
take a look at Object Comparison in the official apple documentation. As you can see there most of the containers use hash to compare objects.
I want to know how NSSet make sure that it has only unique objects
Whenever you try to add object it will check for hash value of all other existing objects inside of it. Based on that it will keep it as unique
If two objects are equal, they must have the same hash value
When we try to add duplicate value, then on which criteria it decides that it has already this value?
if the new object hash value matches the existing then it will be considered as dublicate
Refer this Apple's documentation
First of all the set checks hash values of objects. If hashes are not equal it means that objects are guaranteed to be different. If hashes are equal it however doesn't mean that objects are neccessarily equal, so the set has to make sure and check their isEqual: methods

Comparing object indexes of two Arrays

My problem is...
I have data that gets pulled from a XML file and parsed then stored in an array.
This data is updated every ~60 minutes.
Now i created a second array that parses the same data but only once a week.
What happens now is that i have an array that is updated every 60 minutes and another that is updated every 7 days.
The reason for this, is that i'm doing a table of teams and i want to check when a team has moved up or down in the standings.
So my first issue is tackled, i have 2 arrays containing the teams one with up to date data and the other with a week old data.
My second issue is how do i compare these objects indexes? each object has many properties, as .name or .teamid
Can someone provide me a sample code on how to compare ArrayA Object.name to ArrayB.name?
Thank you so much.
Your question is confusing; apparently you have written an application which is capable of pulling data from XML files, parsing it, creating an array, and doing all this to a time schedule and yet don't know how to compare two items? It sounds like I'm misunderstanding your question! So here is a guess, maybe it will help:
Cocoa has a pattern for equality of objects - two methods isEqual: and hash work together to provide a standardised way of testing whether two object instances are "equal", whatever that means in terms of what the objects represent.
These methods are provided by all the standard classes, so you can test whether two NSString instances represent the same string etc. For a collection class such as NSArray its isEqual: method compares the elements in the two arrays pairwise by calling their isEqual: methods etc.
The base class, NSObject, provides a default implementation, but as it cannot know what equality means for a particular class its implementation is very basic - two objects are equal if and only if they are exactly the same object, i.e. [a isEqual:a] is YES and nothing else is.
So you need to provide an isEqual: and (this is important) hash pair for your class, which implements what is equality for your class - which is probably defined in terms of some of its properties, such as the name you mention, and will invoke their isEqual: methods.
Of course if equality of your class is simply based on one of its properties, such as name, implementing isEqual: & hash is trivial - the implementations just call the same methods on that property. In this case you don't even need to implement the pair yourself, you can access the property can compare it directly. Something like:
NSUInteger indexOfInterest = ...;
MyClass *objectA = arrayA[indexOfInterest];
MyClass *objectB = arrayB[indexOfInterest];
BOOL itemsAreEqual = [objectA.name isEqual:objectB.name];
If your property is not an object type but some primitive type, such as int, then you just use the standard equality operator:
BOOL itemsAreEqual = objectA.name == objectB.name;
Read: Object Comparison from Apple's Cocoa Core Competencies.

Mutable array comparison for copied objects

I am trying to do array comparison on 2 mutable arrays. In one of the array, i am storing my model objects and in the other array I am storing a copy of the model objects using [myObject copy]. My model object is a subclass of NSObject so I have added the copyWithZone: method as well. However when I do array compare using isEqualToArray on these two arrays it always returns false. Will the compare not work on copied objects? Or am I going wrong somewhere else?
P.S: As an overview, what I'm trying to do is to check whether something is changed in my model before calling an update service. I want to call the service Only if any of the model objects have changed.
Will the compare not work on copied objects?
You can very easily find out the answer to this question by just copying a single object and checking for equality agains the original.
SPOILER
The results you are going to see will depend on if you have implemented custom hash and isEqual: methods in your class. Without those it will default to the superclasses implementation (NSObject) which considers equality to be the same pointer. Since a copy is a new pointer to the same object, NSObject won't consider them equal.
I would recommend that you read about object equality in this NSHipster article (great to start with) and/or in this article by Mike Ash (if you are feeling curious)
Method isEqualToArray acts as follows. It takes one by one the next objects from two arrays and compare them using isEqual. The latter compares hash (NSInteger property) of NSObjects (or its subclasses).
In general it is the address of the object. hash can be redefined while subclassing but it may cause big problems.
For copied objects you will have different hashes. And thus isEqualToArray is always FALSE.
But if you use simple data classes like NSNumber, NSString as elements to compare, you will get TRUE under copying them.

Resources