I'm having issues with saving an entity using a UIManagedDocument. I have a NSFetchedResultsController with its context set as the UIManagedDocuments context. I have the controller set up with sections. The user adds an entry to the UIManagedDocuments context and I save the context using:
[context save:&error];
and my NSFetchedResultsController updates properly.
If I leave the view that has the NSFetchedResultsController and then return to the view, in which I create a new NSFetchedResultsController with the same UIManagedDocuments context, I get an error back when I call:
[fetchedResultsController performFetch:&error];
The error says:
CoreData: error: (NSFetchedResultsController) The fetched object at index has an out of order section name '. Objects must be sorted by section name'
and the fetch fails.
If I wait a while before I return to the view I don't get the error. I know that the save operation is done on the child context and then pushes the changes onto the parent context and I think this has something to do with the problem. Does anyone have a solution?
According to the UIManagedDocument Reference, you should not save via the NSManagedObjectContext. Saving should be done via the appropriate UIManagedDocument API(s). Unfortunately, the document is not exactly clear on what you should use.
If you use the undo manager, that path is supposed to make sure everything is appropriately marked as dirty, and saved. Likewise, using an explicit call to
[document updateChangeCount:UIDocumentChangeDone];
is supposed to provide similar functionality. However, saving will be postponed because it is done in a separate thread.
Related
I have two nib files each with its own window populated by data from the same Core Data Managed Object Context (MOC) but each bound to a different array-controller. The problem is that when I delete a data object in one of the window's array-controller, it persists in the other window's array-controller even after saving the common MOC and restarting the program. To permanently remove the unwanted data object, I must remove it at each window separately. This doesn't seem the way Core Data should work. Shouldn't array controllers using the same MOC have a common source of persistent data?
For my setup of the bound array-controller, it is not enough to simply connect the window's 'delete' button to NSControllerArray's 'remove:' method. Instead, I subclass this method and specify a direct MOC delete of the target data-object as follows:
- (void)remove:(id)sender
{
[MOC deleteObject:[[self selectedObjects] lastObject]];
}
thanks in advance for any help. I've spent today battling this and I think that there is something seriously wrong with my understanding of how the framework works.
I'm working on a core data application where the entities have a parent/child relationship. The application creates an NSManagedObjectContext (MOC) on startup. When the application is run for the first time it uses an async block to import the contents of a plist into a second MOC (the root node is obtained from the main MOC using the URI and -managedObjectIDForURIRepresentation:), just before the block finishes it saves the second context.
In my data controller I subscribe to NSManagedObjectContextDidSaveNotification and the following code is run when the notification is sent:
- (void)backgroundContextDidSave:(NSNotification *)notification {
if(![notification.object isEqual:self.managedObjectContext]){
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(backgroundContextDidSave:)
withObject:notification
waitUntilDone:NO];
return;
}
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification]; ;
}
}
I've done a sanity check on this code and sure enough, when the second MOC saves, this is called from the thread executing the block, it is then deferred, and run from the main thread. The notification object contains all objects imported in the second MOC, including the two we're going to be dealing with next.
When this has finished I run the following code which is in a method of the NSManagedObject subclass the objects belong to, it is simply meant to remove a child from its parent:
TreeEntry *oldParent=self.parent; //keep a pointer to the old parent around so we can delete self from the children
// These next four lines are a sanity check to make sure that both objects are on the same MOC we're saving
NSManagedObjectContext *selfContext=self.managedObjectContext;
NSManagedObjectContext *parentContext=self.parent.managedObjectContext;
NSManagedObjectContext *sharedContext=[[DataController sharedDataController] managedObjectContext];
assert([selfContext isEqual:parentContext] && [selfContext isEqual:sharedContext]);
// now we fault the two objects to make sure we can not possibly have them or any changes
// to them in the state of the main MOC, by this time the second MOC is long gone
[sharedContext refreshObject:self.parent mergeChanges:NO];
[sharedContext refreshObject:self mergeChanges:NO];
// up to this point, sharedContex.insertedObjects, sharedContext.updatedObects and sharedContext.deletedObjects
// have all contained no objects at all. None of the above was necessary as the MOC held no changes at all
[sharedContext saveChanges]; // we save it to, well, just to make sure I guess, I may be going crazy
// Now we carry out two changes to the objects, problem occurs if only one change is carried out,
// I'm showing both to show that there relationship is being kept consistent and valid
self.parent=nil;
[oldParent removeChild:self];
// When the next line is run the save fails with a merge conflict
[sharedContext saveChanges];
The last save fails with a Cocoa error 133020, which is a merge fail. The two NSMergeConflicts in the error relate to the entries we're dealing with (self and self.parent).
I just don't understand how that can be. The objects have no state when they are modified so they MUST be getting loaded from the store. Two simple changes are made and then when they are saved straight afterwards a merge conflict occurs. How can that be? nothing else has messed with the store and we've just loaded the objects from it.
I know I can change the merge policy but I don't want to do it without understanding what's going on.
Any ideas? I'm sure it's just that my mental model if what's going on is wrong, but I've not been able to set it right all day!
Ok, it WAS a fundamental misunderstanding on my part of how the framework works, or to be more accurate, the NSManagedStoreCoordinator cache.
When I save the background context the changes go to disk, but apparently the NSManagedStoreCoordinator (which both contexts share) doesn't update or invalidate its cache.
When I refresh the objects in the main MOC the data used to re-populate them comes from the cache, which still has the old data. It doesn't re-load from the disk. The solution was to use [MOC setStalenessInterval:0.0] to force re-loading from disk.
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
I am using a tableview with data from coredata using nsfetchedresultscontroller. When the view loads i make a new entity using
SomeManagedObject *someManagedObject = [NSEntityDescription insertNewObjectForEntityForName:#"SomeManagedObject" inManagedObjectContext:self.managedObjectContext];
This way the new entity appears in my tableview. Now i want this entity to be only temporary, but when i edit some object inside the tableview and save the managedObjectContext the temporary entity will also get saved and i don't want that.
Is their a way to save one object only and not everything inside de managedObjectContext?
Is their some other way to make a temporary object for my tableview.
Any help would be very welcome.
Thanks
Ton
Create the new NSManagedObject with it's alloc init and pass nil instead of the NSManagedObjectContext. Then if you later decide you want that object to be permanent then set it's context. However this will not allow you to see it in a NSFetchedResultsController because it will not be associated with the context.
A better answer can be provided if you could explain what your ultimate goal is.
No, in a managedObjectContext saving is a all or nothing. What I do not know is what happens if you set the persistent store of the managed object to nil
- (void)assignObject:(id)object toPersistentStore:(NSPersistentStore *)store
If you then save the managedObjectContext this object should not be saved. It is just a guess, but tell me if it works ;-)
For temporary managed objects, create them with a 2nd managed object context (MOC). When you are finished, simply release the MOC without performing a save.
Look at the Adding a Book code in CoreDataBooks which uses the same approach to throw away the newly added object when the user cancels.
I think I understand the error message: CoreData could not fulfill a fault, but I am not sure how I should deal with it.
We have an application where we use Core Data to persist data returned from a JSON service. Today I am doing the following.
Fetch local object from persistent store and return to UI
Ask server if the object is updated - when I get the answer, I update the Core Data managed object
Update UI with the updated object
The problem is; even if I do not use multi threads I sometimes gets an error when the HTTP request deletes managed objects that my UI has retained. I tried to fetch the objects with returnsObjectsAsFaults to NO. I thought I then could access all the relations and properties of an managed object even if it was deleted (as long as my UI had retained it).
How should I solve this issue?
I thought I could use separate NSManagedObjectContext for read and write. I have made this test:
MyAuthorMO *authorUpdate = [[MyAuthorMO alloc] init]; // I have made this init insert the object into the updateContext
authorUpdate.firstname = #"Hans";
authorUpdate.lastname = #"Wittenberg";
authorUpdate.email = #"Hans#somedomain.no";
NSManagedObjectContext *updateContext = [[MyCoreManager getInstance] managedObjectContext];
NSError *error = nil;
[updateContext save:&error];
NSManagedObjectContext *readContext = [[MyCoreManager getInstance] readOnlyContext];
NSFetchRequest *fetchRequest = [managedObjectModel fetchRequestFromTemplateWithName:#"authorByEmail" substitutionVariables:[NSDictionary dictionaryWithObject:#"Hans#somedomain.no" forKey:#"EMAIL"]];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSArray *authors = [readContext executeFetchRequest:fetchRequest error:&error];
MyAuthorMO * readAuthor = [authors objectAtIndex:0];
// Delete the author with update context:
[updateContext deleteObject:authorUpdate];
[updateContext save:&error];
NSLog(#"Author: %# %#, (%#)", readAuthor.firstname, readAuthor.lastname, readAuthor.email);
The log is outputted just fine as long as I use the readContext for the fetch. If I use the updateContext for the fetch, I get an exception. This looks promising, but I am afraid that I will run into problems at a later stage. Sooner or later I will probably try to access a property that is not fetched completely (a fault). How can I achieve the behaviour I am looking for?
You shouldn't retain managed objects that the context has released. Let the context do that for you.
The problem is that managed objects can exist as either faults or actualized objects. When you retain one, you may retain the fault which contains no data. Even if you do retain the actual object, the object may not behave properly once it has been separated from its context.
In order to handle your scenario, you need a context for the UI and then a context for the server. After either context makes changes, you should merge the context to ensure both are properly updated relative to the store.
Your UI should be configured to reflect the state of data model, you shouldn't have parts of the data model dependent on the state of the UI.
I had the same problem in my database because I refer to object which didnt exist (because I remove it with other relationed object). My solution was to set "No Action" in my relationship.