ManagedObjectContext Saved In Memory, But Not On Disk - ios

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

Related

NSManagedObjectContextDidSaveNotification useless?

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.

Same NSPredicate fetch different NSManagedObject in different context after merging

I'm working on a multi-thread app. I need to create a NSManagedObjectContext run on background thread (named it privateContext), do some updating, inserting and deleting things on same NSPersistentStoreCoordinator of mainContext. After privateContext is saved, I merge changes of privateContext with mainContext.
If I create a NSManagedObject on mainContext, it is temporary without any save operation and objectID.URIRepresentation is like x-coredata:///ManagedObject/XXX-XXX-XXX-XXX-XXX.
Then I go to background thread and fetch with same NSPredicate on privateContext. The NSManagedObject I get is now x-coredata://XXX-XXX-XXX-XXX-XXX/ManagedObject/p1, which means it's not temporary.
After I do some updating on this object, save privateContext and merge with mainContext. I fetch with same NSPredicate on mainContext, I get a temporary object finally and all changes are gone.
And if I turn the app off and re-open it, all changes are back, and I'll get a persistent object...
EDIT
My question is how to fetch the right object (not temporary one) on mainContext after merging?
EDIT (codes about privateContext and mainContext)
NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[privateContext setPersistentStoreCoordinator:[mainContext persistentStoreCoordinator]];
[privateContext setUndoManager:nil];
// Inserting, updating and deleting on privateContext
if ([privateContext hasChanges]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(privateContextDidSaveNotification:)
name:NSManagedObjectContextDidSaveNotification
object:privateContext];
NSError *saveError;
if (![privateContext save:&saveError]) {
// Error logs
}
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:privateContext];
}
Here's privateContextDidSaveNotification: selector
- (void)privateContextDidSaveNotification:(NSNotification *)notification
{
[mainContext performBlock:^{
[mainContext mergeChangesFromContextDidSaveNotification:notification];
}];
}

iOS Multi-threaded core-data app not merging changes

I have an iOS app which is accessing a core data sql database from two threads. Thread A (the main UI thread) updates a core data record, and Thread B then attempts to read from the Entity collection that Thread A has just updated. Trouble is, Thread B is not 'seeing' the change that Thread A persisted.
Thread B is created by adding an NSOperation subclass object to an NSOperationQueue. The main method of the NSOperation subclass looks like this:
-(void) main {
// NEED to create the MOC here and pass to the methods.
NSManagedObjectContext* moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[moc setUndoManager:nil];
[moc setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy]; // Had been working for months
[moc setPersistentStoreCoordinator:getApp().persistentStoreCoordinator];
[self doTheWorkWithMOC:moc]; // Actually performs updates, using moc
moc = nil;
}
Later, Thread B saves its changes as follows:
#try {
// register for the moc save notification - this is so that other MOCs can be told to merge the changes
[[NSNotificationCenter defaultCenter]
addObserver:getApp()
selector:#selector(handleDidSaveNotification:)
name:NSManagedObjectContextDidSaveNotification
object:moc];
NSError* error = nil;
if ([moc save:&error] == YES)
{
NSLog(#"%s SAVED FINE",__FUNCTION__);
}else {
NSLog(#"%s NOT saved, error=%# %#",__FUNCTION__,error,[error localizedDescription]);
}
// unregister from notification
[[NSNotificationCenter defaultCenter]
removeObserver:getApp()
name:NSManagedObjectContextDidSaveNotification
object:moc];
}
#catch (NSException * e) {
NSLog(#"%s Exception: %#",__FUNCTION__, e);
}
The main UI appdelegate contains the following code to handle the save notification:
- (void)handleDidSaveNotification:(NSNotification*) note
{
#try {
// Notifications run on the same thread as the notification caller.
// However, we need to ensure that any db merges run on the main ui thread.
// Hence:
[self performSelectorOnMainThread:#selector(mergeContexts:) withObject:note waitUntilDone:NO];
}
#catch (NSException * e) {
NSLog(#"appDelegate handleDidSaveNotification Exception: %#", e);
}
}
-(void)mergeContexts:(NSNotification*) note
{
if ([__managedObjectContext tryLock]==YES)
{
[__managedObjectContext mergeChangesFromContextDidSaveNotification:note];
[__managedObjectContext unlock];
}
}
It all works fine most of the time.
However, I have one iPad where the changes written by Thread B are not detected when Thread A reads the database.
Can anyone see anything in my code which would cause this?
Many thanks
Journeyman,
First, you should move to using the queue based MOCs introduced in iOS v5 and Lion. This will make it much easier to keep your two MOCs in sync. You will no longer need to use the locking system.
Second, once you've moved to the queued MOCs, then it is quite straightforward to keep them in sync in response to the "did save" notifications.
Third, why are you always adding and removing the observer for the did save notifications? Doesn't that look suspicious to you? Clearly, you are missing some update between the MOCs.
Andrew

Core Data background thread not updating record

I am having an issue with Core Data in a background GCD thread... I want to update a record, but after fetching it and setting the values it doesn't seem to actually save the updated record.
isUpdate is a BOOL I have setup that tells me whether I am running a first time parse/save or whether it's a record I need to update. In my case, when I update a record it doesn't actually seem to update in my store.
I'm using MagicalRecord helpers. Here's my code:
// Create background context
NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc] init];
[backgroundContext setPersistentStoreCoordinator:[NSPersistentStoreCoordinator defaultStoreCoordinator]];
// Save the background context and handle the save notification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(backgroundContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:backgroundContext];
// Parsing data...
//..
Record *record;
if (!isUpdate) {
record = [NSEntityDescription insertNewObjectForEntityForName:#"Record" inManagedObjectContext:backgroundContext];
} else {
NSPredicate *recordPredicate = [NSPredicate predicateWithFormat:#"SELF.tag == %#", [[node attributeForName:#"tag"] stringValue]];
record = [Record findFirstWithPredicate:recordPredicate];
}
[record setTitle:[[recordNode attributeForName:#"title"] stringValue]];
// Parsing other data...
//..
NSError *error = nil;
// save the context
[backgroundContext save:&error];
if (error) {
NSLog(#"An error occurred: %#", error);
}
And here's the notification:
- (void)backgroundContextDidSave:(NSNotification *)notification {
// Make sure we're on the main thread when updating the main context
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(backgroundContextDidSave:)
withObject:notification
waitUntilDone:NO];
return;
}
// merge in the changes to the main context on the main thread
[[NSManagedObjectContext defaultContext] mergeChangesFromContextDidSaveNotification:notification];
}
Your code sounds quite strange to me.
Why do you register NSManagedObjectContextDidSaveNotification notification in the background thread? Maybe I'm wrong but you need to register that notification in a different point in your app.
If you want to make it works you could register that notification in the main thread. For example you could do it in the AppDelegate.
For example in didFinishLaunchingWithOptions: method you cand do
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(backgroundContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:backgroundContext];
Then, always within the AppDelegate, you can merge the changes with the method you wrote:
- (void)backgroundContextDidSave:(NSNotification *)notification {
// Make sure we're on the main thread when updating the main context
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(backgroundContextDidSave:)
withObject:notification
waitUntilDone:YES];
return;
}
// merge in the changes to the main context on the main thread
[[NSManagedObjectContext defaultContext] mergeChangesFromContextDidSaveNotification:notification];
}
The code performs these steps:
First checks if you are running in the main thread or not.
Since the notification you register could originate from a thread
different from the main one, you need to perform the selector on the
main thread.
Finally performs the merge with the notification that contains the
changes you made in background.
Once done, you can see that the main context is updated with the changes made in the other one.
Edit
Maybe you can try also to change the waitUntilDone to YES.
Hope it helps.
You are mixing two contexts. This code is probably bad:
record = [Record findFirstWithPredicate:recordPredicate];
I assume that this finds record in different context instead of Your backgroundContext. You should change it to something like this:
record = [Record findFirstWithPredicate:recordPredicate inManagedObjectContext:backgroundContext];

ASINetworkQueue inside NSOperation error saving Core Data

I want to use the ASINetworkQueue inside an NSOperation. This works great and makes no problem. What fails is saving Core Data. I set up a new NSManagedObjectContext for this Operation like it is told in the docs.
I think that the problem is that I save the data when the ASINetworkQueue finishes and delegate selector is called. Because the delegates are called on the mainThread, the save message fails.
Can this be the problem and does anybody has a solution?
You are using the PerformSelectorOnMainThread method right (to merge the changes from the new instantiated ManagedObjectContext)?
I do something like this in my Operations (ctx is my instantiated MOC):
First register for notifications:
// Register context with the notification center
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:ctx];
Then when you need to save the context:
if ([ctx hasChanges]) {
error = nil;
// Save the context.
if (![ctx save:&error])
{
// Do something with the error
}
// Clear out the scratchpad
[ctx reset];
}
And then the method that does the merging with the main MOC:
- (void)mergeChanges:(NSNotification *)notification
{
id appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *mainContext = [appDelegate managedObjectContext];
// Merge changes into the main context on the main thread
[mainContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:NO];
// NSLog(#"Merged Changes");
}
Hope this helps

Resources