Working with several NSManagedObject - ios

My question is very simple. I have something like 10 different entities in CoreData, all with the same attributs (name, description ...). To access these attributes i doing in this way:
MyEntity *entity=...;
MyEntity2 *entity2=...;
...
MyEntity10 *entity10=...;
[self myfunction:AnEntity];
After I send a random object to a function
-(void)myfunction:(id)myentity
And here i would like to use a variable which can access the entity attributes whether it's a king of class MyEntity or MyEntity2... The problem is that i can't do:
id myobject=myentity;
NSLog(#"%#", myobject.name);
If someone have a solution to avoid testing the kind of class of the object :)
Thanks !

If you have 10 different entities, I think it's time to move to NSManagedObject subclasses. Then you can define a protocol that encompasses all of the shared attributes, and declare that your NSManagedObject subclasses comply with that protocol. Then your call becomes
-(void)myfunction:(id<SharedAttributesProtocol>)myObject
{
NSLog(#"%#", myObject.name);
}
You mentioned "description" as one of your shared attributes. The -description method is already defined, so you probably want to choose another name for that attribute.
This disadvantage of using a parent NSEntity for the common attributes is that you end up with one very wide table. This table has all of the common attributes, but also has all of the distinct attributes for each of the subentities. Depending on the size of your objects, this will be a performance hit under iOS, although it's not so awful on OS X.

In fact you could call
[myobject valueForKey:#"name"]
or even
[myobject name]
in your function, because the methods are resolved at runtime. If myobject has a "name" attribute, this will work, otherwise it will crash at runtime.
A cleaner solution would be to define one "parent entity" MyEntity with the common attributes name, description etc. Then you can define subentities MyEntity1, MyEntity2, ... which have MyEntity as "Parent Entity". These subentities inherit all attributes/relationships of the parent entity, and can have additional attributes and relationships.
The corresponding managed object subclasses are then subclasses of the MyEntity class. Your function could look like this:
- (void)myfunction:(MyEntity *)myentity
{
NSLog(#"%#", myentity.name);
}
and you can call it with instances of any of the subclasses:
MyEntity1 *myentity1 = ...;
[self myfunction:myentity1];
MyEntity2 *myentity2 = ...;
[self myfunction:myentity2];

Related

Avoid repeating code in NSManagedObject categories

In my entity model I have a top-level "Installation" entity, which has a child "cards" relationship. I also have a "Person" entity, which has a child "cards" relationship.
I've written some code which will sort the NSSet of cards to return a specific subset (called sortedCards), and this function can be performed at either the Installation level, or at the Person level.
For exmaple, I want to be able to call:
NSArray *cards = [installation sortedCards];
as well as:
NSArray *cards = [person sortedCards];
Where am I supposed to put this code so that I don't copy the code in two places? I started by putting it in the Installation NSManagedObject category that I created. But if I do that, i need to copy the code into the Person category as well.
Should I put in an NSSet category and call [installation.cards sortedCards] and [person.cards sortedCards]? That doesn't feel right either.
Any help much appreciated.
Duncan
You should place this method in NSSet category, because categories are used for extending basic functionality. And if you need sort NSSet in different places, it should be NSSet category work, not other object or class.

Inserting one entity into another one

I don't even know how to title this one:
Lets say I have a manufacturer entity and a model entity, with a one-to-many relationship.
Each manufacturer can have multiple models (just using these as an example).
manufacturer has a tableview and its independent fetchedResultsController, then when you press on a manufacturer cell you go to models viewcontroller that also has its own tableview and fetchedResultsController, ofc showing the relevant added models.
Let's say I would like to take one of the models and copy them or cut them into another manufacturer, I was thinking of a method styled like:
-(void)copyThis:(Model*)model toThat:(Manufacturer*)manufacturer
I am grabbing the right manufacturer object and the right model object but how can I implement the insertion of one to another?
To copy
Model *newModel = [NSEntityDescription insertNewObjectForEntityForName:#"Model" inManagedObjectContext:self.context];
newModel.property = model.property; //For every property
model.relationShipName = manufacturer;
[self.context insertObject:copyModel];
To cut
model.relationShipName = manufacturer;
(I assume that you have an xcdatamodeld and have generated an NSManagedObjectSubclass of your Model and Manufacturer entities)
What do you want to achieve with copying? Do you want a 'new' model with the exact parameters added to the other manufacturer, or do you want the relationship to be with the same model object?
Assuming you want to keep a single instance of the Model object:
Manufacturer *fromManufacturer = ...
Model *model = [[fromManufacturer models] objectAtIndex:...];
Manufacturer *toManufacturer = ...
[toManufacturer insertModelObject:model];
if (isCut) [fromManufacturer removeModelObject:model];
To get the insertModelObject and removeModelObject methods automatically, you can use Xcode to generate NSManagedObject subclasses for you automatically. It's under the Editor menu when you're looking at the CoreData Model file. Note that the names of the methods and objects may be different depending on the CoredData model structure and relationship names you've created.

Adding a relationship in core data

I have been at this single task for several days trying to get the relationships between core data entities working. I have achieved this but now I need to change it so that the new attribute value has its relationship added to an existing object. It is a 1 - to - many database.
I am not sure how to add a relationship to a object that already exists. So in the new object that is getting added to RoutineDetail, how would I create the relationship to the object that already exists in the routine Entity?
I have looked at several examples all showing how to add relationships to newly added objects but I need it so the new object in RoutinesDetails has a relationship with the value that already exists in Routines.
The value of Routines is held in a string called RoutineText
rout is the NSmangedObject for the entity Routines
routDet is the NSmanagedObject for the entity RoutinesDetails
I have left the commented out code that allows me to add a relationship when both new objects are created.
This is the last thing I have to do in my project but it is driving me insane. I will be eternally grateful for the fix here. Any advice will be appreciated as this is the best knowledge portal there is. Thank You.
NSManagedObjectContext *context = [self managedObjectContext];
// Create a new device
ExcerciseInfo *info = [_fetchedResultsController objectAtIndexPath:indexPath];
//rout = [NSEntityDescription insertNewObjectForEntityForName:#"Routines" inManagedObjectContext:context];
routdet = [NSEntityDescription insertNewObjectForEntityForName:#"RoutinesDetails" inManagedObjectContext:context];
//Add attribute values
//[rout setValue: RoutineText forKey:#"routinename"];
[routdet setValue: info.name forKey:#"image"];
//Create Relationship
[rout addRoutinedetObject:routdet];
Your main problem statement is, I think, here:
I need it so the new object in RoutinesDetails has a relationship with the value that already exists in Routines.
I presume your data model looks like this:
Routine <----> RoutineDetail
i.e. every one routine has one routine detail (one-to-one relationship).
But this does not really make any sense. You could simply include the attributes of RoutineDetail in the Routine entity.
Instead of
desiredValue = routineDetail.routine.value;
you would simply have
desiredValue = routineDetail.value;
Also, please note that your code has a number of problems. The first line is completely unnecessary, just use self.managedObjectContext. Additionally, against the convention you are using Capital initials for variables. Those should be reserved for class names. Your method to add the relationship also does not look right.
You can add a relationship like this, without a method call:
routineObject.detail = detailObject;

Get the Type of a Core Data relationship

Is there an easy way to find the object type which forms a specific relationship in Core Data?
For example, I have a one-to-many relationship:
Battery-----1-to-Many-----Comment
If I didn't know that the relationship was for a specific Comment object, is there a programatic way I could find out which object type it is, based solely on the set that I'm dealing with.
Something along the lines of
battery.comments.classType = [Comment class]
I'm aware that both Battery and Comment are of type NSManagedObject - I'd like to know more specifically what they are.
I'm also aware that if the NSSet contains any data, I can use any one of it's objects to query the type. However I need to cater for when there is no data in the NSSet.
Thank you.
You can get all info you need from this few lines:
NSRelationshipDescription* rel = [[[battery entity] relationshipsByName] valueForKey:#"comments"];
NSString* className = [[rel destinationEntity] managedObjectClassName];
NSString* entityName = [[rel destinationEntity] name];
In your AppDelegate you have an NSManagedObjectModel typed property. It has an entities array containing NSEntityDescriptions. From here you should be able to figure it out. Hope this helps!

iOS : Core Data: How to retain an ordered set of objects in a managed object

I have a NSManagedObject in my iOS app. This object is called a Round. In the Round I have a to-many relationship to a bunch of Person objects.
xCode generates my managed object class using NSSet as the data type of my to-many relationship to Person managed objects.
So my Round managed object looks like this right:
#interface Round : NSManagedObject
{
}
#property (nonatomic, retain) NSSet* people;
#end
However NSSet is not an ordered collection and I want to retain the ordering of a NSArray I'm using to hold these Person objects as I assign it to my Round managed object.
I tried just converting my NSArray to NSSet, however the original ordering of the set is not retained.
I tried changing the type from NSSet to NSArray in my Round managed object by got the following error at runtime.
2011-03-11 14:00:06.950 SkeetTracker[42782:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: **'Unacceptable type of value for to-many relationship: property = "people"; desired type = NSSet; given type = __NSArrayM;** value = (
" (entity: Person; id: 0x5bed0c0 ; data: {\n firstName = Todd;\n lastName = McFarlane;\n round = \"0x5bf2cb0 \";\n scores = \"\";\n})",
Has anyone ever encountered such a thing and know of a solution?
Kind Regards,
George
I believe that iOS 5 actually has built it in - but regardless, if you want a persistent sorted ordering, you need to have a stored attribute that you can create an NSSortDescription from. Then you add one more method (I prefer a readonly property so I can dot-access it too) which returns the [NSSet sortedArrayUsingDescriptors:self.unsortedSetMethod] array.
This is kind of a complex thing to do, and I wish Apple would build it in. But since they haven't, you have to use a workaround. What I usually do is use a transient attribute with an undefined type, and add an index attribute to the elements in the array. When the data is loaded, you create an array, which is the transient attribute, using the objects sorted by their index. When the context is saved, you go through the array and make sure each object has the correct index. Alternatively, if there are few changes, you can change the index whenever you change the array.
Thx for the great idea. I think in my case I can just add an "index" property to the elements of my array (they're managed objects also) and make the index a transient property.
I was a little unclear why you needed to create the extra transient array attribute.

Resources