views:

464

answers:

1

Hi I have been through the Apple Developer guides and tutorials and I been through 2 iPhone books brushing on the subject of Core Data.

I am used to handling the "value object"/"entity" side of things and then send them of to a web service or the likes. But on the iPhone I get to handle everything myself… cruel world:)

The Locations, TaggedLocations and PhotoLocations examples from Apple Developer site does not give me the answers in a way I can "compute" I hope someone here can enlighten me with the right understanding.

I have set up my model using the datamodel GUI. two entities, "Person" and "Dream". Person has a "personName" string attribute and a one-to-many "dreams" relationship. Dreams has a "description" string attribute and a one-to-one person relationship.

I have been setting up a simple tableView app. First view is a list of persons and the second view is a list of their dreams.

This is how I add a person to the modelObjectContext:

 Person *newPerson = (Person *)[NSEntityDescription
       insertNewObjectForEntityForName:@"Person" 
       inManagedObjectContext:managedObjectContext];

[newPerson setPersonName:@"Ben Hur"];

Ok I then add a new dream to the context:

 Dream *newDream = (Dream *)[NSEntityDescription
    insertNewObjectForEntityForName:@"Dream"
    inManagedObjectContext:managedObjectContext];

[newDream setDescription:@"I had a nightmare"];

I now add the dream to the person like this:

 [newPerson addDreamObject:newDream];

Here it gets a bit hazy to me, because xcode generated different methods/accessors for me on the Person Class:

@class Dream;

@interface Person : NSManagedObject
{ }

@property (nonatomic, retain) NSString * personName; @property (nonatomic, retain) NSSet* dream;

@end

@interface Person (CoreDataGeneratedAccessors) - (void)addDreamObject:(Dream *)value; - (void)removeDreamObject:(Dream *)value; - (void)addDream:(NSSet *)value; - (void)removeDream:(NSSet *)value;

@end

In other situations, where I did not have to handle the actual saving, retrieving, data. I would have build an entity/value object called person and given it an Array to store the dreams. But this is not a possible attribute type in core data, and not the way to do it, I have read(in here in similar threads too).

So how does this boilerplate code work? Am I supposed to use the "addDream" and send it an NSSet filled with dreams? or can I just trust core data to instantiate this and exclusively use the "addDreamObject" send the Person entity objects of type "Dreams"?

I also save the context using the boilerplate code from xcode. Now I wish to update the view with this person, more precisely his name.

In the cellForRowAtIndexPath method I give it this:

 NSManagedObject *managedObject = [fetchedResultsController objectAtIndexPath:indexPath];

cell.textLabel.text = [[managedObject valueForKey:@"personName"] description];

Again all is well and the name is displayed on the list.

I set up my DreamViewController to take a "Person" object as a parameter.

 Person *selectedObject = [[self fetchedResultsController] objectAtIndexPath:indexPath];

dreamView.selectedPerson = selectedObject;

Then I push the viewController onto the stack and we enter the "DreamView". Here I can not seem to get at the at the dreams related to the person I "sent along" with the view.

this is what Im trying in the DreamViewControllers viewDidLoad method(selectedPerson is the accessor I use to pass the Person object):

- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"One Person";

NSManagedObjectContext *context = selectedPerson.managedObjectContext;

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Dream"
                                          inManagedObjectContext:context];
[fetchRequest setEntity:entity];

NSError *error;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
    // Handle the error.
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    exit(-1);  // Fail
}

NSMutableArray *mutableArray = [fetchedObjects mutableCopy];
self.dreamArray = mutableArray;

NSLog(@"the length of dreamArray: %i",[self.dreamArray count] );

Dream *d = [dreamArray objectAtIndex:0]; NSLog(@"The Dream object says: %@", [d description]); [mutableArray release];

[fetchRequest release];

}

I really can't seem to get the hang of this and my current experience with Objective C does not allow me to just grab the "best practice" essence from between the lines of Apples documentation.

Hope someone in here can spare me a little time. Thank You:)

Ricki

+1  A: 

You need to correct a mistake you made in your model, to begin with. You can NOT have an attribute called "description" in your dreams entity: this is forbidden because "description" it's the name of a method.

From the Apple documentation (Core Data programming guide):

Note that a property name cannot be the same as any no-parameter method name of NSObject or NSManagedObject, for example, you cannot give a property the name “description” (see NSPropertyDescription).

The difference between addDreamObject: and addDream: is that the former is used to insert a Dream object in the to-many relationship, while the latter is used to insert or replace one-shot the contexts of the to-many relationship.

You should not use

cell.textLabel.text = [[managedObject valueForKey:@"personName"] description];

you should use simply

cell.textLabel.text = [managedObject valueForKey:@"personName"];

Regarding the dreams related to your person, you do not need an additional fetch request. Once you have your person object, you simply access that person's dreams as follows:

for(Dream *dream in person.dreams){
   // process your Dream object
}

Finally, it is not clear why you do not pass explicitly the managed object context to your DreamViewController as an instance variable. This is common practice, also shown in Apple sample codes. Another error is checking for

if (fetchedObjects == nil)

because it is legal to return nil if the query actually found no objects; you must instead check if your NSError object is not nil (you must initialize it to nil BEFORE executing your fetch request):

if(error)

The statement

NSLog(@"The Dream object says: %@", [d description]);

may even crash your application, as explained at the beginning of my answer.

unforgiven
Thank You:)This was very helpful.I took a couple of hours and re-read the simple "one entity" examples from my books and Apples documentation. Then moved on to the multiple entity examples with your answer in the back of my head. Then it finally made sense.I now pass the "selected person" on to my DreamViewController, because an object always has a reference to it's ManagedObjectContextSo in DreamViewController I can set the objectContext like this:NSManagedObjectContext *context = self.selectedPerson.managedObjectContext;I can scale and understand this approach. Thank You again.
RickiG
You are right, an object always has a reference to it's ManagedObjectContext (it's a property). My suggestion to pass the NSManagedObjectContext to the view controller is just more general and works in all of the cases in which you do not pass a NSManagedObject to the controller but the controller need to create a new NSManagedObject during its course of action.
unforgiven
It came out wrong:)I of course agree that passing the ManagedObjectContext is the safe and also future-proof way to do it. I guess it is a ghost from my Java/Flash thinking that you try to avoid passing a "model" around to every possible controller, be it a singleton or not.Just to be sure I got it right; my self.selectedPerson.managedObjectContext is the persons context(person name, dreams) and not the whole model?If I had passed the original ManagedObjectContext I would have had access to the entire model(other persons and their dreams)?
RickiG
No, it's the whole, global ManagedObjectContext. You need to pass it explicitly and not through an object also for the following reason: if you delete your object and then need to create a new one or deal with others, you can not do this: deleting the object you lose the context as well!
unforgiven
Thank you unforgiven. I see your point, passing just the "selectedObject" and the manipulating it can get you into a deadlock. So a viewController should be passed a "selectedPerson" and a "managedObjectContext". I find the patterns the hardest thing about Objective-C because it has both procedural and objective characteristics.I find myself writing fetchResultControllers for each view, cluttering up my code. Is there a good pattern for wrapping the fetchedResultsController and the managedObjectContext in a class that acts your model and is always "available". Like a singleton model-continue
RickiG
which is often my approach in other objective languages.So that my "model" object has a reference to my managed context and it has a number of methods for returning my data. e.gMyModel.m- (NSArray)dreamsForPersonSortedAZ:(Person *)thePerson atIndexPath:(NSIndexPath *)indexPath{//return a sorted array of the persons dreams}A Class containing methods as the one above would allow me to wrap the 4-5 result sets I will be using again and agin in a single object.Is there a "best practice" for doing something this way or is that not the way it is done in objective C :)Thanks again!
RickiG