views:

459

answers:

2

I'm slightly confused in one aspect of Core Data. That is, when do I use the rudimentary alloc/init routine vs created an object with core data and saving it into the current managed object context.

I know that's a rather vague question, so let me give you an example.

I have an application I'm currently working on that iterates through all of a user's contact book on the iPhone. From there, I wrote a model class called 'Person'. I used to do something like this in a loop of people:

Person *person = [[Person alloc] initWithWrapper:mywrapper];

mywrapper would contain an NSDictionary with the attributes for person. Later I'd be able to populate the address book in my app with the person objects.

Now I've started rebuilding parts of the app with Core Data. Do I continue using the strategy above to populate my address book? Or do I do something like this instead:

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

 [person setName:name];
 [person setDob:dob];

 // Commit the change.
 NSError *error;
 if (![managedObjectContext save:&error]) {
  // Handle the error.
 }

The problem is, this code gets executed everytime the app gets started. Should I not be using core data as it will populate the storage mechanism with redundant instances of person everytime the app loads? Should I modify my NSManagedObject (Person class) and add my initWithWrapper: method and continue as I normally would there?

Slightly confused, would love clarification.

A: 

Yeah, it's simplest to add the initWithWrapper method to your Person class. It would be something like this:

- (id) initWithWrapper:(NSDictionary *)wrapper {
  NSEntityDescription * person = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:someMOC];
  if (self = [super initWithEntity:person insertIntoManagedObjectContext:someMOC]) {
    //do your wrapperly initialization here
  }
  return self;
}

The only downside to this is that this method has to know which managedObjectContext it should insert the object into, so you have to figure out a way to provide that.

That being said, I use this pattern myself all the time.

Dave DeLong
So you're still adding the person object into the managedObjectContext everytime the app starts? If if it's always the same person objects each start? Won't this duplicate Person objects in the data store?
Coocoo4Cocoa
@Coocoo4Cocoa Why not just execute a fetch request with a predicate of "name = %@" and see if it returns any results before adding a new person?
Dave DeLong
+2  A: 

You should never be initializing Core Data objects outside of a managed object context - there's simply no point. Having some

Person *person = [[[Person alloc] init] autorelease];

does you no good since you can't save the object, manipulate it, or really do anything useful that Core Data provides without the context (and thus model and store coordinator) backing it up.

You should instead only use the alloc-init combo when you are inserting an object into Core Data for the first time; this is what the initWithEntity:insertIntoManagedObjectContext: method is for. And you're right, every time you call that method you are inserting a new object into the Core Data context and therefore store, and you may wind up with duplicate objects if you're not careful.

What I would instead recommend for you, if you're running code on every startup, is to come up with a Core Data query that returns some set of existing Person objects, and only add objects (using the initialization method) that don't already exist in the store. If the object already exists, modify it instead of creating a new one.

The trick is getting something like this to perform properly. You shouldn't do a Core Data fetch for every contact in the iPhone address book; many small fetches like this are very expensive. You could in theory get two NSSets - one of Person objects, and one of contacts - then compare them by some unique key (like a hash of the first and last names of the contact). I leave the optimization to you.

The key point is this: don't use alloc and init on a Core Data object unless you mean to insert that object for the first time into a context. Instead look at your existing objects and modify them if necessary.

Tim
Or unless you override init and call super's initWithEntity:insertIntoManagedObjectContext: method. =)
Dave DeLong
Touche. Well done :)
Tim
If anyone has trouble identifying a reason to override init, as Dave mentioned above, I just found one: ObjectiveResource (and, presumably, other non-Core Data-aware dependencies) might try instantiating your classes for you (say, when un-marshalling a server response). That means you may need to override init to work around this exact issue.
Justin Searls