Keeping a CoreData NSManagedObject fresh - ios

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:

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.

Remove all references to ManagedObjects belonging to a ManagedObjectContext

I'm looking to integrate iCloud with a Core-Data-managed SQLite database (only on iOS 7 and later). I've been reading Apple's guide on using Core Data with iCloud (https://developer.apple.com/library/ios/documentation/DataManagement/Conceptual/UsingCoreDataWithiCloudPG/UsingCoreDataWithiCloudPG.pdf).
To quote from the guide, "Core Data posts an NSPersistentStoreCoordinatorStoresWillChangeNotification notification. In your notification handler, you reset your managed object context and drop any references to existing managed objects."
Calling -reset on the MOC to reset it isn't the problem, the problem is the part where they say all references to managed objects need to be dropped. I understand why this needs to be done (because the persistent store is changing), what I don't know is how to do it.
All my Core Data work is handled by a singleton and I had originally thought of posting a notification, and listening classes could set all their managed objects to nil. First, this doesn't sound like a particularly good way of doing it. Secondly, I have a FetchedResultsController managing a tableView, the FetchedResultsController manages it's own managed objects, therefore, as far as I know, I can't set them to nil.
I'd be really grateful for any advice on what to do here.
Thanks in advance.
The way I handle situations like this is to post two notifications in my app: just before resetting, and just after resetting.
For example, I might post MYMainContextWillResetNotification, then reset the context, then post MYMainContextDidResetNotification.
Any controller receiving the will-reset notification should release its managed objects, but also store any information it will need to recover after the reset. Usually this will be one or more NSManagedObjectID objects. In some cases, you may not need to store anything, simply performing a fetch after the reset instead.
A typical method might look like this:
- (void)mainContextWillReset:(NSNotification *)notif
{
self->noteID = note.objectID;
}
This code supposes there is a controller for a single note object. When the reset is about to take place, the note's object identifier is stored in an instance variable.
The did-reset notification method retrieves the note.
- (void)mainContextDidReset:(NSNotification *)notif
{
note = [context existingObjectWithID:noteID error:NULL];
[self refreshViews];
}
This code uses existingObjectWithID:error:, but you could equally do a fetch.
With an NSFetchedResultsController, you would need to call performFetch: in the did-reset method, to refresh the objects.

Implementing CoreData using the parent/child approach with temporary objects

Question: What will be the best way to handle bunch of data that just some of the objects should be saved to disk in coredata?
This is the app/problem:
The app will provide users with the possibility to search for different items on the internet.
The search will return a number of objects that will be displayed to the user.
The user should be able to favorites any of these object at any time. Objects that has been favored should be connected to the current logged in user and live after the app has quit.
The app will have iOS6 as base.
I have been using using these resources
Apple's Core Data Programming Guide: Efficiently Importing Data
Implementing Fast and Efficient Core Data Import on iOS 5
iDeveloperTV CoreData performance course
Im currently looking into the parent/child approach with use of 3 Contexts: Master, Main and Confinement context types.
Current possible solution:
MasterContext that perform save on disk (Has the persistentStoreCoordinator)
MainContext that is used by the UI (child of the masterContext)
BackgroundContext to handle new objects from searches. (child of the mainContext)
So the user may do a search that will return 100 objects (imported on the background context and saved up to the main context).
2 of these objects are favored by the user (on the maincontext). The object will be added to the user and set as "should be saved". (On a save the objects will be pushed up to the master context)
When I save the mastercontext I dont want to save all the 100 objects to disk. Just the two objects the user have favored.
So im think about deleting the object that should not be saved to disk just before I do a save on the mastercontext.
- (void) mainContextHasSaved: (NSNotification *) notification {
NSLog(#"Lets save the master");
[_masterManagedObjectContext performBlock:^{
//Loop through all inserted object and check if they should be saved to disk
[self removeObjectThatShouldNotBeSavedToDisk];
NSError *error = nil;
BOOL saveSuccess = [_masterManagedObjectContext save:&error];
if(saveSuccess) {
//Do something
}
}];
}
But after what I understood is that when a save is performed on a parent context, all the changes will be propagated to the children. Then I will loose all the objects except the two that has been stored.
So does anyone know how to solve this kind of problem? Is it something I can do in the example presented above? Or should I create multiple persistentStores and move objects between contexts?
Thanks to all that is willing to help and if more information is needed just ask :)
In a similar project I used this solution which was also favored by the users:
Keep a time stamp attribute in the downloaded items and delete them when the time stamp is older than a certain threshold and they are not marked as favorite.

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