NSManagedObjectContextDidSaveNotification useless? - ios

I am using Core Data for a while now with a background contex, and was wondering why everyone advise to use the NSManagedObjectContextDidSaveNotification for merging from the background to the main context. I created a Test-Project with one NSPersistentStoreCoordinator, a main context and a background context. Here is the code fragment for initalisation:
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
- (NSManagedObjectContext *)backgroundContext {
if (_backgroundContext != nil) {
return _backgroundContext;
}
_backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_backgroundContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
return _backgroundContext;
}
until now, i would have listened to the save notification like this:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:self.backgroundContext];
But i realised, it doesn't matter if I merge from that notification. I can edit and save either context, and the other one get's merged after some seconds itself.
So. my question, why would i even need the NSManagedObjectContextDidSaveNotification?

Your contexts are not related. They are both root contexts attached to the same persistent store coordinator.
A change to the persistent store is automatically pushed to the root contexts associated with it (which is why you don't need to handle the NSManagedObjectContextDidSaveNotification notification.)
NSManagedObjectContextDidSaveNotification is useful when dealing with more complex context ancestry, since a mid-level context does not automatically notify all of its children when changed.
As an example, check out the architecture diagram for Cadmium (https://github.com/jmfieldman/Cadmium). When a background child context saves up to the writer context, the main context must handle a NSManagedObjectContextDidSaveNotification on the main thread to incorporate the updates.

Related

ManagedObjectContext Saved In Memory, But Not On Disk

I am in need of assistance... been at this for too long.
What generally is the issue when I can correctly query an NSManagedObject but when I go to check the actual datastore (after multiple context saves without fails) the data doesn't exist?
My hypothesis is that the ONLY logical conclusion is that there is a threading issue. Is this correct?
Depends.
If you querying objects from context, that is directly attached to persistent store, than all should be ok.
If you querying them from in-memory context, which have that directly-to-persitent-store-attached as it's parent context (which is common case for multithreaded CoreData usage), then you should push changes to parent context and then call -[save:] on that parent context.
Note. I assumed, you do perform change merges for child MO contexts. Do you? Example code:
// Core-Data MOC creation
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
_managedObjectContext = ... ; // creating context, if not yet
...
if (_managedObjectContext != nil) {
[_managedObjectContext performBlockAndWait:^{
// //NOTE: iCloud-related
// [[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(remoteChangesImport:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:coordinator];
}];
}
// register merge callback for child contexts!
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(localChangesSave:)
name:NSManagedObjectContextDidSaveNotification
object:_managedObjectContext];
return _managedObjectContext;
}
- (void)localChangesSave:(NSNotification *)notification {
// Changes in child context. Need to merge...
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}

Need access to same instance of ManagedObjectContext when performing rollback functionality from AppDelegate in Core Data

Inside my iOS application, I am using Core Data to do a fetch, and delete of a very large data set. This process takes approximately 5-10 seconds. What I would like to do is perform a rollback in case the user decides to turn the device off before the process has completed. However, the problem is to have the SAME instance of the NSManagedObjectContext to call the rollback function from the appropriate AppDelegate method. Within my application, I call my Core Data methods using a Singleton object like this:
static MySingleton *sharedSingleton = nil;
+ (MySingleton *) sharedInstance {
if (sharedSingleton == nil) {
sharedSingleton = [[super alloc] init];
}
return sharedSingleton;
}
In my application, I return an instance of an NSManagedObjectContext like this:
- (NSManagedObjectContext *) managedObjectContext{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
//Undo Support
NSUndoManager *anUndoManager = [[NSUndoManager alloc] init];
[self.managedObjectContext setUndoManager:anUndoManager];
}
return _managedObjectContext;
}
I then call it, and assign it to a reference like this:
NSManagedObjectContext *context = [[MySingleton sharedInstance] managedObjectContext];
How would I make this instance of the ManagedObjectContext available to me for use in the AppDelegate, so that I can call the rollback function?
First off, the better (safer) way to create a singleton is as in the example given here: Create singleton using GCD's dispatch_once in Objective C, namely:
+ (instancetype)sharedInstance
{
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
Next, since you've created a managed object context that must hang around for a few seconds, you must have a strong reference to it somewhere, which you do.
If you're in the midst of debugging and are questioning some fundamental assumptions about your code, give the managed object context a name (or log the memory address of the MOC pointer), that you can inspect in the debugger later, to verify for yourself that indeed, you're dealing with the same one.
Note also that if you created a dedicated Managed Object Context just for this sort of importing, you wouldn't need to roll it back. You could just discard it.
In my apps, I usually have a parent (root) managed object context and a couple of child contexts; one child is for the main thread, another child is for import type operations.
As an alternative solution, instead of rollback the changes, you can create a multi context scenario where a child managed object context add all the data that you need and eventually when its done, you save the child context sending the new data to the main managed object. This way the main managed object context its not affected until the complete process is done.
This is a great article as reference Multi-Context CoreData.
Basically what you need to do is
// create main MOC
_mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainContext setPersistentStoreCoordinator:_persistentStoreCoordinator];
// create child MOC
_childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_childContext.parentContext = _mainContext;
Hope it helps.

Updating UI while importing large data sets in Core Data

In my app I have a function that will download a large data set from a server into Core Data. It looks something like this:
for (PFObject *historyObject in historyArray) {
History *history = (History *)[NSEntityDescription insertNewObjectForEntityForName:#"History" inManagedObjectContext:managedObjectContext];
history.date = historyObject.date;
// ...
}
Because this can take a few minutes I want to update UI in the process and show the user the progress that has been made.
Now because inserting this data into Core Data is blocking the main thread I am not sure if there is another proper way to update the UI.
I have tried several methods of moving the insertion into a different thread but have always had problems with Core Date responding with errors.
I hope somebody out there has a good idea and would like to share it.
Any help is much appreciated!
I have currently solved this by initializing the ManagedObjectContext with concurrency type NSPrivateQueueConcurrencyType and setting the parent context to the main ManagedObjectContext.
For anybody having the same problem:
- (void)doSomething
{
_backgroundMOC = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_backgroundMOC setParentContext:[kDelegate managedObjectContext]];
for (int i = 0; i < [cars count]; i++)
{
[_backgroundMOC performBlockAndWait:^{
Drive *drive = (Drive *)[NSEntityDescription insertNewObjectForEntityForName:#"Drive" inManagedObjectContext:_backgroundMOC.parentContext];
....do more stuff...
}];
}
[self performSelectorOnMainThread:#selector(showProgress:) withObject:[NSNumber numberWithFloat:((float)i/(float)[cars count])] waitUntilDone:NO];
}
For this to work you will have to change the managedObjectContext in the AppDelegate:
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil)
{
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}

Core data data not saved

I am currently developing an application that uses Core Data to store data. The application synchronizes its content with a web server by downloading and parsing a huge XML file (about 40000 entries). The application allows the user to search data and modify it (CRUD). The fetch operations are too heavy, that is why i decided to use the following pattern :
"One managed object context for the main thread (NSMainQueueConcurrencyType) in order to refresh user interface. The heavy fetching and updates are done through multiple background managed object contexts (NSPrivateQueueConcurrencyType). No use of children contexts".
I fetch some objects into an array (let us say array of "users"), then i try to update or delete one "user" (the object "user" is obtained from the populated array)in a background context and finally i save that context.
I am listening to NSManagedObjectContextDidSaveNotification and merge any modifications with my main thread managed object context.
Every thing works fine except when i relaunch my application i realize that none of the modifications has been saved.
Here is some code to explain the used pattern
Main managed object context :
-(NSManagedObjectContext *)mainManagedObjectContext {
if (_mainManagedObjectContext != nil)
{
return _mainManagedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
_mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainManagedObjectContext setPersistentStoreCoordinator:coordinator];
return _mainManagedObjectContext;
}
Background managed object context :
-(NSManagedObjectContext *)newManagedObjectContext {
NSManagedObjectContext *newContext;
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
newContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[newContext performBlockAndWait:^{
[newContext setPersistentStoreCoordinator:coordinator];
}];
return newContext;
}
Update a record :
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
FootBallCoach *coach = [_coaches objectAtIndex:indexPath.row];
coach.firstName = [NSString stringWithFormat:#"Coach %i",indexPath.row];
NSManagedObjectContext *context = [[SDCoreDataController sharedInstance] newManagedObjectContext];
[context performBlock:^{
NSError *error;
[context save:&error];
if (error)
{
NSLog(#"ERROR SAVING : %#",error.localizedDescription);
}
dispatch_async(dispatch_get_main_queue(), ^{
[self refreshCoaches:nil];
});
}];
}
Am i missing any thing ? should i save my main managed object context after saving the background context ?
If your context is configured with a persistent store coordinator, then save should write data to the store. If your context is configured with another context as parent, then save will push the data to the parent. Only when the last parent, the one that is configured with persistent store coordinator is saved, is the data written to the store.
Check that your background context is really configured with persistent store coordinator.
Check the return value and possible error of the -save:.
Make sure you work with your background context via -performBlock...: methods.
UPDATE
Each time you call your -newManagedObjectContext method, a new context is created. This context knows nothing about FootBallCoach object you’re updating. You need to save the same context FootBallCoach object belongs to.
Don’t forget that each object belongs to one and only one context.
Also make sure you hold a strong reference to a context whose objects you’re using.

iOS background save in Core Data

I want to ensure that my main thread never blocks, that's why I want to do my Core Data saves in the background.
I've been reading the Apple docs together with this link (and many others, but I found this one pretty useful): http://www.cocoanetics.com/2012/07/multi-context-coredata/, though I cannot get the architecture right.
In my AppDelegate.m:
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_saveContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_saveContext setPersistentStoreCoordinator:coordinator];
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setParentContext:_saveContext];
}
return _managedObjectContext;
}
Then to save, I would do something like this:
// var 'context' is the context coming from method managedObjectContext
// this code is in the same thread 'context' is created in (the main thread)
NSError *error = nil;
if ([context save:&error]) {
[context.parentContext performBlock:^{
NSError *err = nil;
if(![context.parentContext save:&err]) {
NSLog(#"Error while saving context to the persistent store");
}
}];
} else {
// handle error
}
This is what I would get from reading the link I supplied earlier. Saving does not work, once the app is closed and reopened, the changes made to any managed objects are gone: they were never saved to the persisted store.
Makes sense I guess since I created 2 NSManagedObjectContexts in 1 thread, Apple docs clearly state to have only 1 NSManagedObjectContext per thread. So how do I setup the parent/child relation between _managedObjectContext and _saveContext? I know _saveContext needs to be initialised in another thread, but I cannot get this approach to work.
(From the comments)
All the "new" managed object context types (NSMainQueueConcurrencyType, NSPrivateQueueConcurrencyType) manage their own threads, it is not necessary to create the context on a special thread. The only thing to remember is always to use performBlock or performBlockAndWait for operations in the context. This ensures that the operations are executed on the right queue and thread.
So your code is OK.
(As it turned out, the error was that a wrong context was passed to your saving routine and therefore the inner save was not done in the top-level context.)

Resources