Core Data: Can't Use Previously Saved Object - iphone

I am really stuck with these two things.
What I am trying to do:
My entity is simple. It's a "record".
It has a "name (NSString)" and "parent (relationShip)"
"parent" connect to itself, entity "record".
Ok, now I want to create "parentRecord" and "simpleRecord".
I try to do with that code:
groupRecord = (record *)[NSEntityDescription insertNewObjectForEntityForName:#"record"
inManagedObjectContext:self.managedObjectContext];
groupRecord.name = GroupTextField.text;
[self saveContext];
It's "parentRecord", I save it for a future use, and catch in "groupRecord" variable.
Now I have to create a "simpleRecord". This is a code:
record *newRecord = (record *)[NSEntityDescription
insertNewObjectForEntityForName:#"record"
inManagedObjectContext:self.managedObjectContext];
newRecord.name = textField.text;
[newRecord setMyParent:groupRecord]; //and it crashes here!
I rearranged this code, so *I don't do [self saveContext]; * in "parentRecord".
Just use it from variable groupRecord. And save it in "childRecord" block. Then all is fine. Records save to storage and I can read it from there.
Why does it happens? What should I do, if I want to create "parentRecord" first, SAVE IT ,and later - "childRecord"?
Why can't I use previously saved object? NSManagedObjectContext is the same - what's wrong?
I am good enough with "classic" SQL, but Core Data is killing my brain.
Thanks to everyone.
Update:
Look, saveContext is out of reasons to crash. Here is:
Create parent entity.
Set it to variable of appDelegate.
Save context (for a parent).
Create childEntity.
Set parentProperty from variable of appDelegate. App crashes!
And:
Create parent entity.
Set it to variable of appDelegate.
///////////Save context (for a parent).
Create childEntity.
Set parentProperty from variable of appDelegate. No any crash.
Savecontext this time.
All is fine now.
Parent property - is just a name of the property. It is not some additional setup for a parent in MOM file.
I want to do entity with hierarchy.
And there is NO some additional methods, that Xcode create for me - just a properties.

Okay it sounds like you have a simple data model that looks like this (pseudocode):
Record{
name:string
parent-->Record
}
This is dangerous because there is no reciprocal relationship. This can lead to orphaned objects and compromise the integrity of the object graph. Instead use:
Record{
name:string
parent<--(optional)-->Record.child
child<--(optional)-->Record.parent
}
Now, you have a simple, one dimensional linked list like an array or set. Except for the topmost record object, every record object has a parent and expect for the bottommost object each has a child. To assign one to each you would do:
Record *firstRec; //assuming you have created a custom class for Record
Record *secRec;
firstRec=[NSEntityDescription insertNewObjectForEntityForName:#"Record"
inManagedObjectContext:self.managedObjectContext];
//-------------------------------------^
secRec=[NSEntityDescription insertNewObjectForEntityForName:#"Record"
inManagedObjectContext:self.managedObjectContext];
//-------------------------------------^
firstRec.name=someText;
secRec,name=someOtherText;
firstRec.child=secRec;
[self saveContext];
Now if you want a tree structure in which each parent can have more than one child, you would have an object model like so:
Record{
name:string
parent<--(optional)-->>Record.child
child<<--(optional)-->Record.parent
}
Your insertion and assignments then change to:
Record *firstRec;
Record *secRec;
firstRec=[NSEntityDescription insertNewObjectForEntityForName:#"Record"
inManagedObjectContext:self.managedObjectContext];
//-------------------------------------^
secRec=[NSEntityDescription insertNewObjectForEntityForName:#"Record"
inManagedObjectContext:self.managedObjectContext];
//-------------------------------------^
firstRec.name=someText;
secRec.name=someOtherText;
[firstRec.addChildObject:secRec];
// or
secRec.parent=firsRec;
[self saveContext];
The reason is that a to-many relationship requires a method to add the new object to set. Which cannot be done with a simple assignment. The child, however, only has one parent so it can use a simple assignment. Since the relationship is reciprocal, assigning to one object automatically assigns to the object on the other side of the relationship.
That is how it should work. The errors you are seeing most likely come from having the wrong object model. If you have one-to-one, required relationships like this:
Record{
name:string
parent<--(required)-->Record.child
child<--(required)-->Record.parent
}
... you will encounter problems when you try to save if either a parent or child is missing. Likewise, if you try to assign multiple objects to a to-one relationship, you can get the error you are seeing.
You should never use the cast when doing an insertion because if you have a mismatch between the assigned class and the cast class, the runtime will force the other class into the cast causing all kinds of strange errors.
I can't say for certain exactly what your problem is because I can't see your object model. This however, should point you in the right direction.

Would you share code for "saveContext" and for "setMyParent"?
NSManagedObjectContext has -(BOOL)save:(NSError**)error method. Is that being called within "saveContext"?
And, if your relationship is called "parent", then you should be setting relationship with something like -addParentObject: ... which would be declared in your Record.h file. Xcode will do this for you, if you do things in a certain order. Otherwise, you will need to write the method declarations yourself.

Related

iPhone Core Data - cannot fulfill a fault error

I have three classes, A, B and C. A is the main class.
When the user wants to see the list of all objects that were purchased, Class B is called from A and shows the list of objects in a core data entity.
Inside class B, the user can buy new objects (in-app purchase). When the user wants to buy another object, class C is called.
When class C is called, a new object is created on the core data entity using
anObject = [NSEntityDescription insertNewObjectForEntityForName:#"Objects" inManagedObjectContext:context];
this object is then assigned to a local reference on Class C, using something like
self.object = anObject;
this object variable was declared like this:
.h
MyObjects *object;
#property (nonatomic, retain) MyObjects *object;
and #synthesized on .m
MyObjects is a core data class representing the entity.
In theory, object will retain anything assigned to it, so the line self.object = anObject I typed previously will retain anObject reference on self.object, right?
The problem is that when I try to access self.object in the same class after buying the new object, I receive an error "CoreData could not fulfill a fault for XXX", where XXX is exactly self.object.
At no point in the code there's any object removal from the database. The only operation to the database I could identify was a saving operation done by another class moments before the crash. The save is done by something like
if (![self.managedObjectContext save:&error]) ...
Is there any relation? what may be causing that?
CoreData manages the lifetime of managed objects and you should not retain and release them. If you want to keep a reference to the object so that it can be retrieved later then you have to store the object's id (obtained using -[NSManagedObject objectID]). Then use that to retrieve the object later using -[NSManagedObjectContext objectWithID:].
Make sure you understand about CoreData faulting. Read the documentation.
I had a similar issue a few days ago (using NSFetchedResultsController) where I was placing my fetchedObjects into an array and gathering attributes to populate tables from the array objects. It seems that if the objects in the array are faulted, you cannot unfault it unless you are acting on the direct object. In my case, I solved the issue by taking the lines of code in question and calling [[_fetchedResultsController objectAtIndexPath:indexPath] someAttribute]. I would assume that doing something similar would fix your problem as well. It seems a bit tedious to need to fetch from the managedObjectContext to obtain a faulted value, but this was the only way I could personally get past the issue.
Core Data is responsible for managing the lifetime of managed objects in memory. It's really important to understand Managed Object Contexts - Read the documentation.
Apple also provides an entire troubleshooting section here, and it contains among other things the causes for your error. But it's really only useful if you understand how core data works.
Most likely error is that the object you are saving does not belong to the managed object context.
Say you use the same object on different threads and those different threads use different managed object context, then this will happen.

CoreData Relationships Lazy Load?

When I have a CoreData entity named, say, 'Book', which has a one-to-one relationship with another entity ('Bookmark') would I need to insert both an instance of 'Book' and an instance of 'Bookmark' into a managed object context, and manually assign the relationship?
If I perform the following code to instantiate an instance of 'Book'...
Book *book = [NSEntityDescription insertNewObjectForEntityForName:#"Book" inManagedObjectContext:managedObjectContext];
Can I, or can I not, perform the following code to retrieve its bookmark without inserting an instance of 'Bookmark' into the same managed object context?
book.bookmark;
Will CoreData allocate a new instance of 'Bookmark' for me?
Before you ask me to try this and see for myself: I have. I'm either doing something wrong, or I'm meant to instantiate both instances of 'Book' and 'Bookmark' and assign the relationship manually. In my testing, the relationship has 'optional' unticked but is not attached to anything. Upon invoking the getter 'bookmark' on a 'book' instance, I receive 'nil'.
I'd like to be able to use my code above, as it will allow my controller objects to deal with the managed object context, while my model objects can ignore it entirely. From what I've read of CoreData, this seems to be the intended implementation. Please correct me if I'm wrong.
If a Book object always requires a Bookmark relationship, then you can write a custom getter method for the bookmark property that will insert a bookmark object when needed.
Xcode will generate most to the method for you. In the data model editor control-click on the bookmark and choose Copy Objective-C 2.0 Implementation to the clipboard. Then tweak something like this:
- (NSManagedObject *)bookmark
{
id tmpObject;
[self willAccessValueForKey:#"bookmark"];
tmpObject = [self primitiveBookmark];
[self didAccessValueForKey:#"bookmark"];
if (tmpObject==nil){
BookmarkMO *newBookmark=[NSEntityDescription entityForName:#"Bookmark" inManagedObjectContext:self.managedObjectContext];
newBookmark.book=self; //triggers kvo relationship assignment
tmpObject=newBookmark;
}
return tmpObject;
}
In my experience you have to create both entities and create the relationship yourself first.
something like this should be what you need:
Bookmark *bookmark = [NSEntityDescription insertNewObjectForEntityForName:#"Bookmark" inManagedObjectContext:managedObjectContext];
[book setBookmark: bookmark];

Is there a way to instantiate a NSManagedObject without inserting it?

I have a user interface to insert a Transaction. once the user clicks on a plus he gets the screen and i want to instantiate my Core Data NSManagedObject entity let the user work on it. Then when the user clicks on the Save button i will call the save function.
so down to code:
transaction = (Transaction *)[NSEntityDescription insertNewObjectForEntityForName:#"Transaction" inManagedObjectContext:self.managedObjectContext];
//even if i dont call save: its going to show up on my table
[self.managedObjectContext save:&error]
P.S i am using an NSFetchedResultsController on that table and I see that the NSFetchedResultsController is inserting a section and an object to the table.
My thought is if there is a way to instantiate the Transaction NSManagedObject i could update it with out saving untill the client choses to.
For what it's worth, Marcus Zarra seems to be promoting the nil context approach, claiming that it's expensive to create a new context. For more details, see this answer to a similar question.
Update
I'm currently using the nil context approach and have encountered something that might be of interest to others. To create a managed object without a context, you use the initWithEntity:insertIntoManagedObjectContext: method of NSManagedObject. According to Apple's documentation for this method:
If context is not nil, this method
invokes [context insertObject:self]
(which causes awakeFromInsert to be
invoked).
The implication here is important. Using a nil context when creating a managed object will prevent insertObject: from being called and therefore prevent awakeFromInsert from being called. Consequently, any object initialization or setting of default property values done in awakeFromInsert will not happen automatically when using a nil context.
Bottom line: When using a managed object without a context, awakeFromInsert will not be called automatically and you may need extra code to compensate.
here is how i worked it out:
On load, where we know we are dealing with a new transaction, i created an out of context one.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Transaction" inManagedObjectContext:self.managedObjectContext];
transaction = (Transaction *)[[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
then when it came to establishing a relation ship i did this:
if( transaction.managedObjectContext == nil){
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Category" inManagedObjectContext:self.managedObjectContext];
Category *category = (Category *)[[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
category.title = ((Category *)obj).title;
transaction.category = category;
[category release];
}
else {
transaction.category = (Category *)obj;
}
and at the end to save:
if (transaction.managedObjectContext == nil) {
[self.managedObjectContext insertObject:transaction.category];
[self.managedObjectContext insertObject:transaction];
}
//NSLog(#"\n saving transaction\n%#", self.transaction);
NSError *error;
if (![self.managedObjectContext save:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
There's a fundamental problem with using a nil MOC: Objects in different MOCs aren't supposed to reference each other — this presumably also applies when one side of a relationship has a nil MOC. What happens if you save? (What happens when another part of your app saves?)
If your object doesn't have relationships, then there are plenty of things you can do (like NSCoding).
You might be able to use -[NSManagedObject isInserted] in NSPredicate (presumably it's YES between inserting and successfully saving). Alternatively, you can use a transient property with the same behaviour (set it to YES in awakeFromInsert and NO in willSave). Both of these may be problematic if a different part of your app saves.
Using a second MOC is how CoreData is "supposed" to be used, though; it handles conflict detection and resolution for you automatically. Of course, you don't want to create a new MOC each time there's a change; it might be vaguely sensible to have one MOC for unsaved changes by the slow "user thread" if you don't mind some parts of the UI seeing unsaved changes in other parts (the overhead of inter-MOC communication is negligible).
You can insert an NSManagedObjectContext with the -[NSManagedObject initWithEntity:insertIntoManagedObjectContext:], passing nil for the managed object context. You must, of course, assign it to a context (using -[NSManageObjectContext insertObject:] before saving. This is, as far as I know, not really the intended pattern in Core Data, however (but see #mzarra's answer here). There are some tricky ordering issues (i.e. making sure the instance gets assigned to a context before it expects to have one, etc.). The more standard pattern is to create a new managed object context and insert your new object into that context. When the user saves, save the context, and handle the NSManagedObjectDidSaveNotification to merge the changes into your 'main' context. If the user cancels the transaction, you just blow away the context and go on with your business.
An NSManagedObject can be created using the nil as the context, but if there other NSManagedObjects it must link to it will result in an error. The way I do it I pass the context into the destination screen and create a NSManagedObject in that screen. Make all the changes link other NSManagedObjects. If the user taps the cancel button I delete the NSManagedObject and save the context. If the user taps the the save button I update the data in the NSManagedObject, save it to the context, and release the screen. In the source screen I update the table with a reload.
Deleting the NSManagedObject in the destination screen gives core data time to update the file. This is usually enough time for you not to see the change in the tableview. In the iPhone Calendar app you have a delay from the time it saves to the time it shows up in the tableview. This could be considered a good thing from a UI stand point that your user will focus on the row that was just added. I hope this helps.
transaction = (Transaction *)[NSEntityDescription insertNewObjectForEntityForName:#"Transaction" inManagedObjectContext:nil];
if the last param is nil, it will return a NSManagedObject without save to db

Can I create an new instance of my custom managed object class without going through NSEntityDescription?

From an Apple example, I have this:
Event *event = (Event*)[NSEntityDescription
insertNewObjectForEntityForName:#"Event"
inManagedObjectContext:self.managedObjectContext];
Event inherits from NSManagedObject. Is there a way to avoid this weird call to NSEntityDescription and instead just alloc+init somehow directly the Event class? Would I have to write my own initializer that just does that stuff above? Or is NSManagedObject already intelligent enough to do that?
NSManagedObject provides a method called initWithEntity:insertIntoManagedObjectContext:. You can use this to do a more traditional alloc/init pair. Keep in mind that the object this returns is not autoreleased.
I've run into the exact same problem. It turns out you can completely create an entity and not add it to the store at first, then make some checks on it and if everything is good insert it into the store. I use it during an XML parsing session where I only want to insert entities once they have been properly and entirely parsed.
First you need to create the entity:
// This line creates the proper description using the managed context and entity name.
// Note that it uses the managed object context
NSEntityDescription *ent = [NSEntityDescription entityForName:#"Location" inManagedObjectContext:[self managedContext]];
// This line initialized the entity but does not insert it into the managed object context.
currentEntity = [[Location alloc] initWithEntity:ent insertIntoManagedObjectContext:nil];
Then once you are happy with the processing you can simply insert your entity into the store:
[self managedContext] insertObject:currentEntity
Note that in those examples the currentEntity object has been defined in a header file as follows:
id currentEntity
To get it to work properly, there is a LOT of stuff to do. -insertNewObject:... is by far the easiest way, weird or not. The documentation says:
A managed object differs from other
objects in three main ways—a managed
object ... Exists in an environment
defined by its managed object context
... there is therefore a lot of work
to do to create a new managed object
and properly integrate it into the
Core Data infrastructure ... you are
discouraged from overriding
initWithEntity:insertIntoManagedObjectContext:
That said, you can still do it (read further down the page to which I linked) but your goal appears to be "easier" or "less weird". I'd say the method you feel is weird is actually the simplest, most normal way.
I found a definitive answer from More iPhone 3 Development by Dave Mark and Jeff LeMarche.
If it really bothers you that you use a method on NSEntityDescrpiton rather than on NSManagedObjectContext to insert a new object into an NSManagedObjectContext, you can use a category to add an instance method to NSManagedObjectContext.
Create two new text files called NSManagedObject-Insert.h and NSManagedObject-Insert.m.
In NSManagedObject-Insert.h, place the following code:
import <Cocoa/Cocoa.h>
#interface NSManagedObjectContext (insert)
- (NSManagedObject *)insertNewEntityWithName:(NSString *)name;
#end
In NSManagedObject-Insert.m, place this code:
#import "NSManagedObjectContext-insert.h"
#implementation NSManagedObjectContext (insert)
- (NSManagedObject *)insertNewEntityWithName:(NSString *)name
{
return [NSEntityDescription insertNewObjectForEntityForName:name inManagedObjectContext:self];
}
#end
You can import NSManagedObject-Insert.h anywhere you wish to use this new method. Then replace the insert calls against NSEntityDescription, like this one:
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
with the shorter and more intuitive one:
[context insertNewEntityWithName:[entity name]];
Aren't categories grand?

Model instantiation question when using Core Data

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.
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.
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.