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

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.

Related

Core Data: A clever way to implement Add/Edit functionality

I've created a couple of Apps that use Core Data and a lot of experiments, but I've never found the "perfect" way to implement a simple Add/Edit viewController.
I just want to implement a single controller able to manage both the edit and add functionalities, I don't want to create two different Controllers.
At the moment I'm working whit this approach (let's take the classic Person NSManagedObject as example)
1) In the addEditViewController I add a currentPerson property
var currentPerson:Person?
2) When I present the controller in Add-Mode this property is nil
3) When I present the controller in Edit-Mode this property is a reference to the Person to edit
4) When I need to save user operations I just check if the currentPerson is set and I understand if I need to create a new object in the context or just save the one I need to edit.
Ok, this approach works but I want to follow another approach that seems to be more secure for the edit action. Check this terrible error!
Let's say that the person has Address property that needs a different viewController to be edited.
1) Following my previous logic I can pass the currentPerson property to the addressViewController that I'm going to present:
addressVC.currentPerson = currentPerson
presentAddressVC()
2) Now when the user has completed the edit operation and he/she taps on "save"
the addressVC calls the saveContext function.
Where is the problem? well... if the user starts editing the currentPerson in the addEditViewController an then just goes back to a previous controller, the currentPerson still stores the edit of the user and as soon as the context will be saved in any other controller the not-really-wanted data get stored and becomes persistent.
Probably I can perform a rollback in case the user taps the back button on the addEditViewController, but I really don't like this behaviour it seems so poor.
I think to work with multiple contexts or inserting NSManagedObjects in a nil context and just move them to the main context only at the end of the operations but I'm not sure about this choice too.
I know it's a kind of a complex and long (an tedious) question, but I hope you can give me some lights on this issue.
How to you treat this kind of situation? what do you think about my approach and about my proposed approaches?
It seems to me that your problem is maintaining a connection to a single NSManagedObjectContext when instead what you really want is to establish a tree. The construction of a context is fairly cheap so you should be creating a context per ViewController.
So when you show the addEdit controller you can simply create it with a new context:
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy;
context.parentContext = //parentContext
context.undoManager = nil;
Think of these new contexts as scratch pads for editing your managed objects. The only thing to bear in mind is that when you call save, it saves to the parent context and not all the way to the store. For that you will need a recursive call all the way to the parent for saves. Heres a basic recursive save:
- (void)saveChangesToDiskForManagedObjectContext:(NSManagedObjectContext *)context {
if (context && [context hasChanges]) {
[context save:nil];
NSManagedObjectContext *parentContext = context.parentContext;
[parentContext performBlock:^{
[self saveChangesToDiskForManagedObjectContext:parentContext];
}];
}
}
Its not really great practice to retain managed objects in an app where they could be deleted on other screens. So what you should do is perhaps fetch these objects in your view controllers view will appear method. Either that or call refreshObject(mergeChanges:) to synchronize your managed object with the changes made by another screen.
I really don't like the idea of calling save when the user navigates back, there should be a save button, when i press back I'm trying to close the screen, i would expect to select "done" if i want my changes saved.
Don't forget you can also use an NSUndoManager to track all your changes, thats why the context has an undoManager:)

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.

Keeping a CoreData NSManagedObject fresh

Imagine a social photo app like Instagram. You see one of your friend's photos in your feed. That photo is persisted in CoreData as something like this:
Photo (NSManagedObject)
Attributes: id, imageURL
Relationships: comments (set of Comment objects), likes (set of Like objects)
I have a view controller that has a reference to the Photo object. This view controller also handles actions for liking and commenting on the photo.
So, when someone likes a photo the flow is this: Send the like to the server. If the API post is successful, update the Photo object in CoreData with any new information received from the server, which will include the new like. At this point the Photo should have one more Like object related to it in CoreData than before I pressed the like button.
Now here is where I'm having a problem. The view controller needs to update the number of likes on the photo. In a success block, I'm doing this:
self.likesLabel.text = [NSString stringWithFormat:#"%d likes", self.photo.likes.count];
The problem is that self.photo.likes.count is reporting 0 at this point (it was non-zero before I pressed the like button). If I navigate elsewhere and come back to this screen, the count will update properly. So it seems that the Photo managedObject becomes "dirty" when I update it. The update probably happens in another context (I use Magical Record). The update looks something like this:
NSManagedObjectContext* context = [NSManagedObjectContext MR_contextForCurrentThread];
Photo* photo = [Photo MR_findFirstWithPredicate:somePredicate inContext:context];
...
photo.likes = likes;
photo.comments = comments;
[photo save:context];
So the question is, how do I properly keep the Photo object updated in the view controller so that I can always reliably query the number of likes and comments? In general, how can you always keep a fresh version of an NSMangagedObject, given that it may be updated in other threads/contexts?
I have tried listening for the NSManagedObjectContextObjectsDidChangeNotification, but I run into the same problem where the Photo reports 0 likes and comments when I get that notification.
One solution for this scenario I frequently use is KVO. In your case, I would observe the "likes" property on the managed object. You can look up standard KVO articles on how to do that properly. However, this is only half the story. So, in order for those values to change for you properly, the NSManagedObjectContext that is "managing" that property needs to get updates itself. This is where either merging saves on NSManagedOjbectContextDidSaveNotification events in the case of a thread isolation context, or the parent/child merge rules come into play. The point is, you could be making the change to the actual data on any context within your app. It's up to you to merge in those changes into the context you currently care about (probably what's displaying on the screen).
All that said, MagicalRecord should have some build in methods to already handle that half of the problem for you. If not, make sure your context that is displaying use information is being updated properly when a background context is being saved. The thing to remember is that you have to update the context, and once the context merges in the new changes on update, then your KVO methods will fire with the proper values.
listen and apply merges made in other contexts
#pragma mark - Core Data stack
- (void)mergeChanges:(NSNotification *)notification {
// Merge changes into the main context on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self.mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
[[NSNotificationCenter defaultCenter] postNotificationName:MyMainContextMergedChangesNotification object:nil];
});
}
after the merge you can refresh the GUI
alternativly/additionally you can force the context to refresh the object from the db calling refreshObject:

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

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

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];

Resources