Consistency between the same database accessed in the appdelegate and a view controller - ios

I've been working with Core Data and UIManagedDocument lately, and have some questions.
I've a UITableViewController in which I open a UIManagedDocument. Here I download info to the tableViewCotroller and save the info to the database and update the tableview. Now when I enter the main screen via the home button and then open the app again, I want to do work with the data from the database in the appdelegate . So in the appropriate method in appdelegate, I open the database, fetch the entities I want and change the properties in the objects I want. Now, this works, I manage to open the database and get the objects I want From the database.
My problem occours when I'm done changing data from the database in the appdelegate. Because after the appdelegate method is completed, I try to refetch data in the tableview from the same database, but at this point of time the database hasn't registered the changes made in the method called in the appdelegate. I've tried calling the saveToURL for overwriting method on the UIManagedDocument right after the changes are made inside the appdelegate and ive also tried the save: method on the managedobjectcontext.
What's the right way of saving the changes so that database gets updated?
Ps: I'm writing this on an iPad, so I havnt managed to upload code, I'll upload it later.
Thanks

That is the right way - make changes in a NSManagedObjectContext then save. Remember that saveToURL is asynchronous, so what is most likely the problem is that your save has not completed by the time you perform the fetch in the table view. That's where the completion block comes in. So maybe you could have the completion block set a property in your table view which causes your fetch to happen. Something like
// appdelegate
[self.mainDatabase saveToURL:self.mainDatabase.fileURL
forSaveOperation:UIDocumentSaveForOverwriting
completionHandler:^(BOOL success) {
if (success) {
myTableView.context = self.mainDatabase.managedObjectContext;
}
}];
// TableView
- (void)setContext:(NSManagedObjectContext *)newContext {
_context = newContext;
NSArray *fetchResults = [_context executeFetchRequest:someFetchRequest error:&error];
// update model etc.
}

Related

What am I supposed to do in processContentChanges:?

This is my app's processContentChanges: method, which is triggered by NSPersistentStoreDidImportUbiquitousContentChangesNotification:
- (void)processContentChanges:(NSNotification *)notification {
[self.managedObjectContext performBlock:^{
// Merge incoming data updates in the managed object context
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
// Post notification to trigger UI updates
#warning What do I actually do here?
}];
}
I'm using NSFetchedResultsControllers throughout my app so that the UI updates automatically when changes are received from another device through iCloud. This all seems to work, but the comment saying // Post notification to trigger UI updates was there in the template method already. Am I actually supposed to do something here, or can I safely leave things the way they are?
Well, although I haven't had confirmation of this I don't think there is anything else that needs to be done in this method as long as the following criteria are met:
You implement NSPersistentStoreDidImportUbiquitousContentChangesNotification correctly as per the template so that new content is merged to the managed object context
Your content is generated using NSFetchedResultsController objects
Your viewControllers conforms to the the NSFetchedResultsControllerDelegate protocol, and implements controllerWillChangeContent:, controllerDidChangeContent: and controller:didChangeObject:atIndexPath:forChangeType:newIndexPath
In those methods, update your views accordingly to display new content, remove deleted content, and update changed content.
If you have any objects which utilise CoreData without an NSFetchedResultsController then you might need to update these by manually re-fetching the data when NSPersistentStoreDidImportUbiquitousContentChangesNotification is posted by CoreData.

UIManagedDocument parent context object insertion on a background priority queue not updating in UI using child context

I'm trying to implement some basic UIManagedDocument import/export functionality into my app, mainly for dev so that I can easily inspect the document contents and more crucially preserve a set of test data when I start trying to iterate on my CoreData models.
All I am trying to do is load some JSON data from a local file and inject it into my apps UIManagedDocument. The UIManagedDocument's ManagedObjectContext contents are visualised in my app using some Core Data Table View Controllers from the Stanford iOS courses.
I thought I'd try to write this with some threading to keep the UI responsive and to learn how to do it. So I've done something like this
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// read JSON data file
// parse JSON into dictionaries etc
// iterate over dictionaries
// try to create a new core data entity with my document's ManagedObjectContext
} );
At first I thought this was working. No errors, asserts, crashes triggered and upon looking at my CoreData TableViews I could see the newly added objects in my UI. Unfortunately the newly added objects were seemingly never saved back to the store. I even hooked up to listen to the NSManagedObjectContextDidSaveNotification from my UIDocument's managedObjectContext and saw it wasn't triggering on pressing the home button, like it usually does if it has some changes performed in the app with my UI pending. Infact even doing these operations in the UI wouldn't cause the notification and saving to occur so it was clearly not happy.
I unrolled the code from within the background queue and ran it on the main thread synchronously and everything worked ok, the new data was saved correctly.
I started reading about the complexities of threading and coredata, the documentation seemed to suggest using the UIDocument's ManagedObjectContext's parent ManagedObjectContext to perform operations on in the background so I tried doing the same code again using this parent context, so as follows
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// read JSON data file
// parse JSON into dictionaries etc
// iterate over dictionaries
// try to create a new core data entity with my document's ManagedObjectContext parent ManagedObjectContext
} );
This time for some reason the CoreData TableView controllers no longer updated to show the newly injected objects. Even after explicitly calling save on the parent context, nothing appeared. However on quitting the app and reloading the app the newly injected objects did seem to be added correctly. Interestingly at this point i'd left a fetchrequest with a cachename specified and that threw up an error on this first run of the app after injecting the objects this way. I guess somehow the way the object had come from the parent context directly perhaps invalidated the cache somehow, that's still something I don't fully understand. Even changing the cache to nil didn't fix the issue of the table views not updated the same session as when the objects were injected into the parent context.
Looking elsewhere I've seen some uses of the managedObjectContext performBlock suggested. Another case where someone has said you must call
[document updateChangeCount:UIDocumentChangeDone]
after all changes to ensure the saving is performed, or perhaps using
- (void)autosaveWithCompletionHandler:(void (^)(BOOL success))completionHandler
instead. Though elsewhere I've seen mentioned that saving should be enough to push context contents through the hierarchy. Does saving only work from child -> parent and not from parent -> child.
Or am I just doing it wrong?
Anyone's time and help is really appreciated! Cheers.
please look at the 'Parent/Child Contexts' section in the Multi-Context CoreData.
Whenever a child MOC saves the parent learns about these changes and this causes the fetched results controllers to be informed about these changes as well. This does not yet persist the data however, since the background MOCs don’t know about the PSC. To get the data to disk you need an additional saveContext: on the main queue MOC.
I do saving to the PSC n following way:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
{
// read JSON data file
// parse JSON into dictionaries etc
// iterate over dictionaries
NSError* error;
if (self.managedObjectContext.hasChanges)
{
[self.managedObjectContext save: &error];
}
// Save main context
dispatch_sync(dispatch_get_main_queue(), ^
{
[[AXContextHelper sharedInstance] saveMainContext];
});
}
After looking into all the suggestions I could find for similar problems, none of them seemed to help.
The scenario i was describing in the end had the store stuff to file handled ok, but the UI not updating. At the time when I do this import/export I'm in a view ontop of the core data table view controller that doesn't update when I inject all these JSON objects, so in the end all I did was force that controller to re-fetch it's data. I believe the NSFetchedResultsController is meant to monitor the managedObjectContext and update the fetch request as required. For whatever reason, this wasn't working.
With the force re-fetch called, everything seems to work ok. The newly injected entities appear in my tables, and are saved to the store files.
Either there are some bugs in these objects, or I'm still using it wrong but I see other problems... my import/export data both work now on background threads with the parent context. What I've noticed today is that I can use the UI to properly insert and edit some objects using the child context on the main thread. I don't deliberately call anything at the moment after making these edits so i guess the edits are are still pending till core data decides to save. If i then go to my export feature and use it using the parent context then i find the newly edited or inserted objects aren't there.
Clearly there's no messaging happening in time from child -> parent, and messaging from background thread edits on the the parent itself just doesn't seem to work.. I never get any notifications for the edits i perform in a thread on the parent context, even though they seem to work. If I force the save to happen via the documents then the passed in dictionaries show no edits being made, even though the edits are saved correctly to store files somehow.
I guess i now need to force my document to save after every operation, or at least before every operation i'm about to do on another thread to ensure the parent/child are both in sync.
Still the problem I originally described I've managed to get around, but I'm still left wondering quite how I should be using the parent and child context of a UIManagedDocument to avoid encountering these issues.

save: on a NSManagedObjectContext not working

In a navigation controller, I go from view controller 1 to view controller 2.
View Controller 1 is a table view controller tied to a FetchedResultsController. It fetches data from a specific context and displays it. I then segue to view controller 2 if a row is tapped. While segueing, I set a particular NSManagedObject property of view controller 2 using the data I have in view controller 1.
Now, in view controller 2 I am able to show data using the NSManagedObject property and then also make changes to it and perform a save:. When I go back to view controller 1 , the change is reflected. However, if I re-launch the app it is not being reflected any more in any of the view controllers.
This is how I am doing the save.
- (void)hiddenAttributeSwitchSlid:(UISwitch *)sender
{
[self.workoutType.managedObjectContext performBlock:^{
self.workoutType.workoutHiddenByDefault = [NSNumber numberWithBool:sender.isOn];
NSError *error = nil;
if (![self.workoutType.managedObjectContext save:&error]) {
NSLog(#"There was an error in saving to context - %#", [error localizedDescription]);
}
else {
NSLog(#"No error");
}
}];
}
workoutType is a NSManagedObject which is set in the prepareForSegue: before segueing to this view controller.
Even if I do not use the performBlock:, it doesn't work.
PS - I know questions of this kind have been asked before. I browsed through them, but nothing seems to be working.
Do you have a default value set for that attribute in your model?
There is an identified bug in Core Data where in some situations (I have not narrowed it all the way down) where an attribute with a default value gets reset several times during the backgrounding of an application.
The way to test for this is to listen for the value being changed via KVO and log the changes then duplicate your test. If you see several changes then you know you are hitting that bug.
The only known solution I have seen that is reliable is to remove the default value. If a default value is required then I would add it to the NSManagedObject subclass in the -awakeFromInsert method and then update the validation methods to check for it. Sucks I know.
Update #2
How many contexts do you have?
Are you using parent child contexts?
If so, are you saving the top most parent?
Update #3
Ok, a UIManagedDocument has two NSManagedObjectContext instances inside of it. Is there a reason you are using a UIManagedDocument? Do you have more than one document in your application? If you don't then I strongly suggest you switch back to a traditional Core Data stack. The UIManagedDocument is not really meant to be in a single stack application.
As for the saving issue directly, UIManagedDocument tries to save in the background when you exit the application. If can take a bit of time and, personally, is not very reliable. You can request a save on exit which will help to insure the save is speedy, but even then it can be unreliable.
How are you exiting the application?
Are you killing it in Xcode?
Are you backgrounding it and then resuming it?
Are you backgrounding it and then killing it from the iOS device?
You can listen for the UIManagedDocument to save and then print a log statement so you can see when the saves actually happen to disk. That might be useful to help narrow down exactly when it is and is not saving.
I think you shouldn't use saving on context, the document will auto save.
There might be a data consistency error when the document is being saved. I propose to you not to use saving of the context by [self.workoutType.managedObjectContext save:&error] and use the following class inherited from UIManagedDocument which adds logging on auto-save:
LoggingManagedDocument.h:
#interface LoggingManagedDocument : UIManagedDocument
#end
LoggingManagedDocument.m:
#import "LoggingManagedDocument.h"
#implementation LoggingManagedDocument
- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError
{
NSLog(#"Auto-Saving Document");
return [super contentsForType:typeName error:outError];
}
- (void)handleError:(NSError *)error userInteractionPermitted:(BOOL)userInteractionPermitted
{
NSLog(#"error: %# userInfo: %#", error, error.userInfo);
}
#end
This class should help you identify the problem, why your data is not saved. Just let the application run and wait 15-30 seconds after you make the change of the attribute and auto-save should occur.
The logging is based on: http://blog.stevex.net/2011/12/uimanageddocument-autosave-troubleshooting/
From the UIManageDocument documentation:
The UIManagedDocument architecture leads to several considerations:
You should typically use the standard UIDocument methods to save the document.
If you save the child context directly, you only commit changes to the parent context and not to the document store. If you save the parent context directly, you sidestep other important operations that the document performs.
The easiest option is to use a saveless model, which means using the NSUndoManager of the document. I usually do [self.workoutType.managedObjectContext.undoManager beginUndoGrouping]; before adding the changes and [self.workoutType.managedObjectContext.undoManager endUndoGrouping]; after editing.
You need to save the document:
[document saveToURL:document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL];

Not getting current data in UITableView after XML Parse (using NSFetchedResultsController)

Struggling a bit here...
My view controller adheres to following protocols
In my init method I will check a remote server to get an updated XML file... parse the XML file, and write the contents to Core Data.
My tableview's content is managed with NSFetchedResultsController that displays this Core Data.
My Problem:
NSFetchedResultsController seems to be getting the data before the Core Data update from the remote file takes place. I've verified the database is being updated properly and if I run a second time the TableView will show the correct data.
Maybe I'm just not doing the reloadData in the proper place? I have implemented
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[myTableView reloadData];
}
Also, after the parser completes and the new data has been written to core data I'm trying this:
-(void)parserDidEndDocument:(NSXMLParser *)parser {
[myTableView reloadData];
}
Anyone have any ideas? Let me know what extra code might be useful to post. Thanks!
you may want to check the following (from Apple's docs)
A controller thus effectively has three modes of operation, determined by whether it has a delegate and whether the cache file name is set.
No tracking: the delegate is set to nil.
The controller simply provides access to the data as it was when the fetch was executed.
Memory-only tracking: the delegate is non-nil and the file cache name is set to nil.
The controller monitors objects in its result set and updates section and ordering information in response to relevant changes.
Full persistent tracking: the delegate and the file cache name are non-nil.
The controller monitors objects in its result set and updates section and ordering information in response to relevant changes. The controller maintains a persistent cache of the results of its computation.
it sounds like you want full persistent tracking. So you probably want to make sure you have the delegate set (which you probably already have done) and set the cache to non nil
You may also want to make sure you are saving your managedObjectContext after you are done parsing. After saving, make sure to perform the fetch again.
NSError *error;
BOOL success = [controller performFetch:&error];
if (!success)
NSLog(#"Core Data Fetch Error: %#"error);
It could be that the app is saving the context when it is exiting and that is why you are seeing the data when you relaunch.
Good luck

iOS - Core data - NSManagedObjectContext - not sure if it is saved

Overview
I have an iOS project in which I am using Core data
I am inserting an object, then I want to save it.
I am not sure if save works.
Save seems to be working when app goes into background
When using Simulator, If I click on Stop button on Xcode, save doesn't seem to be working.
Question
Is the save actually happening ?
Am I facing a problem because I created a view based app (the core data checkbox was not available) ?
Steps Followed
I am using the simulator to test it.
Insert an object (code is in the next section)
Save the inserted object (code is in the next section)
I press the Stop button on Xcode to stop running the app
Output noticed
setBeforeSave.count = 1
setAfterSave.count = 0
Before saving, The NSManagedObjectContext method insertedObjects returns 1 object
Before saving, The NSManagedObjectContext method insertedObjects returns 0 objects
When Xcode Stop button is pressed, and when the app is relaunched, the previous data is not available (is it because I clicked on stop on xcode)
managedObjectContext is NOT nil
The NSManagedObjectContext method save: returns YES.
Code to Insert Object
Test *test = [NSEntityDescription insertNewObjectForEntityForName:#"Test" inManagedObjectContext:self.database.managedObjectContext];
Code to Save:
//database is a property of the type UIManagedDocument
NSSet *setBeforeSave = [self.database.managedObjectContext insertedObjects];
NSLog(#"setBeforeSave.count = %i", setBeforeSave.count);
NSError *error = nil;
if(![self.database.managedObjectContext save:&error])
NSLog(#"error = %#", error);
NSSet *setAfterSave = [self.database.managedObjectContext insertedObjects];
NSLog(#"setAfterSave.count = %i", setAfterSave.count);
According to the UIManagedDocument documentation, you should not call save on either of the internal managed contexts. Instead, if you want data saved, you should do one of two things.
Use the undoManager, as it will mark the context dirty, and ready to be saved.
Call [document updateChangeCount:UIDocumentChangeDone];
Thus, in your case, you should replace that save call with:
[self.database updateChangeCount:UIDocumentChangeDone];
And your data will get saved.
EDIT
To provide additional detail. A UIManagedDocument has two MOCs., in a parent/child relationship. The child is the one you get when calling document.managedObjectContext. Now, when a NSManagedObjectContext has a parent, the normal way to propagate changes to the parent is to call save:. However, the UIManagedDocuememt does other stuff, and its documentation specifically says NOT to call save on either the parent or child context.
Well, how does stuff get saved, then? Well, you tell the UIManagedDocument that it is "dirty" and needs to be saved. The two ways you can do that are by either using the undoManager, or calling updateChangeCount:.
When doing either of those, the internals of UIManagedDocument will make sure that the parent context is notified of the change. At some point in the future, the parent will effect the change to the actual backing store (i.e., file(s) on disk).
Furthermore, when a context is "saved" it may or may not keep references to the objects that were changed. You can set a property which tells it to retain objects that have been saved, or to release them.
Hopefully, that addresses your problems.
to summarize, though, see the original answer.
BTW, you can actually see a log of what the SQL store is doing underneath by adding "-com.apple.CoreData.SQLDebug 1" to your command line arguments. You do that in the "Edit Scheme" dialog.

Resources