Saving context for every action - ios

I have network park of my application which discovering devices in network and inserting into coredata through Magical Record. But this is happened on some other thread not main thread. Also I have UITableView with fetchresultcontroller which showing devices in table view. But only option how to let fetchRequestController now about my changes was, calling this:
[[NSManagedObjectContext MR_contextForCurrentThread] MR_saveToPersistentStoreAndWait];
After every change. Missing I am something, or is this correct way how to make it?
Example:
[MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext){
MyCDDevice * localDevice = [MyCDDevice MR_createEntityInContext:localContext];
[localDevice initFromDictionary:dictionary];
}];
[[NSManagedObjectContext MR_contextForCurrentThread] MR_saveToPersistentStoreAndWait];

Save on every whole data operation, which is recommended by MagicalRecord. After saveWithBlockAndWait:, it is saved to persistent store, so there is no need to MR_saveToPersistentStoreAndWait.

Related

How to properly save updates with MagicalRecord?

I'm using MagicalRecord, and I can't understand how to make it work stable and predictable.
When I need to update some entities, I retrieve them from DB, change them according to the logic, after then I send them into my “Saver” method:
- (void) saveEntities:(NSArray *)entities {
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
for (Entity_class *entityElement in entities) {
NSPredicate *entitySearchPredicate = [...] // Composing predicate
Entity_class *foundEntity = [Entity_class MR_findFirstWithPredicate:entitySearchPredicate];
foundEntity = entityElement;
}
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
} completion:^(BOOL contextDidSave, NSError *error) {
// contextDidSave always equals NO. Sometimes changes get saved, but sometimes they don't
}];
}
I've tried to save local context [localContext MR_saveToPersistentStoreAndWait] instead of the default one, but it never worked.
I'm struggling with these contexts for the second night, and just I've run out of the search query variants for Google. How to deal with context and save them properly?
There are a few important notes to understand using CoreData:
You must save using the context the entity was created in.
Meaning, if entities were created on a different thread they might be using a different context and sometimes that can cause faults when having multiple threads and a lot of saves-per-second.
The "saveWithBlock" method is for asynchronous saving, meaning it will save the context once its ready (usually its immediately), so if you try and check for changes right away and fetch the data, it might not have been saved yet.
It will help you to read more about the differences in core data between the different contexts - MainQueue and PrivateQueue.
Usually i'd use the given methods:
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait]
And
[[NSManagedObjectContext MR_contextForCurrentThread] MR_saveToPersistentStoreAndWait]
You can also use the MR_saveToPersistentStoreWithCompletion: for asynchronous saving.
This way it saves the entire context without the need to select specific entities.
If you are new to it, you can also check CoreDataStack
Which is a more recent and up-to-date core data wrapper

NSManagedObject values are correct, then incorrect when merging changes from parent to child NSManagedObjectContext

I’m having a Core Data issue while using 2 NSManagedObjectContext, running on different threads, and migrating changes from the parent to the child. In essence, I’m able to pull the changes from parent to child, but immediately after doing so, the changes are lost.
The app I’m building is a test for syncing a model between multiple devices and a server.
The context that holds the objects the user interacts with is on the main thread and is configured as a child of the sync context and is created like this (error checks omitted)
NSManagedObjectContext *parentMOC = self.syncManagedObjectContext;
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext performBlockAndWait:^() {
[_managedObjectContext setParentContext:parentMOC];
}];
The syncManagedObjectContext is the parent context and is where the syncManager performs the sync with the server. It gathers up objects modified by the user, sends the changes to the server and merges changes received back. The syncManagedObjectContext also sends its data to the PersistentStoreCoordinator to be stored in SQLite.The context runs on a background “thread” so that syncing and storing does not block the main thread. Here’s how it is created:
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
_syncManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_syncManagedObjectContext performBlockAndWait:^(){
[_syncManagedObjectContext setPersistentStoreCoordinator:coordinator];
}];
Sync Logic Flow
The syncing is kicked off when the syncManager handles the NSManagedObjectContextObjectsDidChangeNotification from the main context. Here is a rough flow of what happens:
syncManager handles NSManagedObjectContextObjectsDidChangeNotification which lets it know that objects have been changed in the main thread context. It calls save on the main context which saves the changes to syncMOC.
When the syncManager receives NSManagedObjectContextDidSaveNotification to indicate the save has been completed, it gathers up the newly changed objects from the sync context and sends the changes to the server. It then does a save on the sync MOC which sends the data to SQLite. Note that each object has a uuid field which I create as a portable id - not be confused with Core Data’s objectID, as well as a lastSynced timestamp that is provided by the server.
The server responds back with updated timestamps for the objects sent, as well as any other changes that have happened. In the simplest case that illustrates the issue, what is received is a set of records that consists of the uuid and the updated lastSynced time for the objects that the syncManager just sent.
For each update, the syncManager updates the object in the syncContext and stores the NSManagedObject objectID for the object (not the uuid) in an array.
The syncManager then does a save on the on the sync MOC to write the data to disk and posts a message to provide the main MOC with the array of objectID’s for updated objects. At this point, if I do a fetch for all Entities in the syncMOC and dump them to the log, they all have the correct values. Further, if I look at the SQLite database on disk, it too has the correct values.
Here’s abbreviated code (some error checking and non-essential stuff removed) for how the updates are merged in on the main thread, with comments as to what’s happening: (Note: I’ve been careful in the code to use performBlock and it appears from tracing in the debugger that everything is happening on the correct thread.)
-(void)syncUpdatedObjects: (NSNotification *)notification
{
NSDictionary *userInfo = notification.userInfo;
NSArray *updates = [userInfo objectForKey:#"updates"];
NSManagedObjectContext *ctx = self.managedObjectContext;
[ctx performBlock:^() {
NSError *error = nil;
for (NSManagedObjectID *objID in updates) {
NSManagedObject *o = [ctx existingObjectWithID:objID error:&error];
// if I print out o or inspect in the debugger, it has the correct, updated values.
if (o) {
[ctx refreshObject:o mergeChanges:YES];
// refreshObject causes o to be replaced by a fault, though the NSLog statement will pull it back.
// NOTE: I’ve used mergeChanges:NO and it doesn’t matter
NSLog(#"uuid=%#, lastSynced = %#", [o valueForKey:#"uuid”], [o valueForKey:#"lastSynced"]);
// Output: uuid=B689F28F-60DA-4E78-9841-1B932204C882, lastSynced = 2014-01-15 05:36:21 +0000
// This is correct. The object has been updated with the lastSynced value from the server.
}
}
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#“MyItem"
inManagedObjectContext:ctx];
request.entity = entity;
NSArray *results = [ctx executeFetchRequest:request error:&error];
for (MyItem *item in results)
NSLog(#"Item uuid %# lastSynced %# ", item.uuid, item.lastSynced);
// Output: uuid B689F28F-60DA-4E78-9841-1B932204C882 lastSynced 1970-01-01 00:00:00 +0000
// Now the objects have incorrect values!
}];
}
In case you missed it, the issue is there in the comments after the NSLog statements. The object initially has the correct values from the parent context, but then they become incorrect. Look at the timestamp, specifically.
Does anyone have any idea why this would happen? I should note that the business of doing the fetch at the end was part of debugging. I was noticing that the NSManagedObjects being held on to in the program did not have the correct values, even though I was seeing that things were updated correctly in the above code and through uniquing, they should be updated too. I thought that what might be happening is that I was creating “extra” objects with the correct values while the old ones were still around. However, the fetch showed that the right objects and only the right ones were around, only with bad values.
One more thing, if I do the same fetch in the parent context after this function runs, it shows the correct values as does SQLite.
Any help is much appreciated!
I finally found the answer to this issue and hope it might help others.
What I noticed at some point is that the object ID coming back to the main context had incorrect
Core Data ID - they should have been permanent, but were not. And in fact, during the merge, I
realized that the ID for a given object in my main MOC and the ID for the changes to merge for
that object were both temporary, but different. But neither were the permanent ID that they should have been.
A search on Stack Overflow for that issue led me to
this answer https://stackoverflow.com/a/11996957/1892468 which gives a workaround for a known Core
Data bug.
So the problem wasn't that I was doing it wrong, it was that Core Data was not doing what it
said it would do. The workaround is that during the save operation on the main object context I added the following
code prior to calling save.
if ctx.insertedObjects.count > 0 {
do {
try ctx.obtainPermanentIDsForObjects(Array(ctx.insertedObjects))
} catch {
log.error("Unable toobtain permanent ids for inserts")
}
}
That fixed it! So what I had originally been observing was that the merge was not actually taking
place. There were 2 objects alive for what was supposed to be one.
You could simply subscribe to NSManagedObjectContextDidSaveNotification of the sync context and then merge changes into UI context by calling -mergeChangesFromContextDidSaveNotification:.

best way to concurrently fetch data from a server and modify core data graph as the data arrives? [duplicate]

Question: How do I get my child context to see changes persisted on the parent context so that they trigger my NSFetchedResultsController to update the UI?
Here's the setup:
You've got an app that downloads and adds lots of XML data (about 2 million records, each roughly the size of a normal paragraph of text) The .sqlite file becomes about 500 MB in size. Adding this content into Core Data takes time, but you want the user to be able to use the app while the data loads into the data store incrementally. It's got to be invisible and imperceptible to the user that large amounts of data are being moved around, so no hangs, no jitters: scrolls like butter. Still, the app is more useful, the more data is added to it, so we can't wait forever for the data to be added to the Core Data store. In code this means I'd really like to avoid code like this in the import code:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
The app is iOS 5 only so the slowest device it needs to support is an iPhone 3GS.
Here are the resources I've used so far to develop my current solution:
Apple's Core Data Programming Guide: Efficiently Importing Data
Use Autorelease Pools to keep the memory down
Relationships Cost. Import flat, then patch up relationships at the end
Don't query if you can help it, it slows things down in an O(n^2) manner
Import in Batches: save, reset, drain and repeat
Turn off the Undo Manager on import
iDeveloper TV - Core Data Performance
Use 3 Contexts: Master, Main and Confinement context types
iDeveloper TV - Core Data for Mac, iPhone & iPad Update
Running saves on other queues with performBlock makes things fast.
Encryption slows things down, turn it off if you can.
Importing and Displaying Large Data Sets in Core Data by Marcus Zarra
You can slow down the import by giving time to the current run loop,
so things feel smooth to the user.
Sample Code proves that it is possible to do large imports and keep the UI responsive, but not as fast as with 3 contexts and async saving to disk.
My Current Solution
I've got 3 instances of NSManagedObjectContext:
masterManagedObjectContext - This is the context that has the NSPersistentStoreCoordinator and is responsible for saving to disk. I do this so my saves can be asynchronous and therefore very fast. I create it on launch like this:
masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
mainManagedObjectContext - This is the context the UI uses everywhere. It is a child of the masterManagedObjectContext. I create it like this:
mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];
backgroundContext - This context is created in my NSOperation subclass that is responsible for importing the XML data into Core Data. I create it in the operation's main method and link it to the master context there.
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
This actually works very, VERY fast. Just by doing this 3 context setup I was able to improve my import speed by over 10x! Honestly, this is hard to believe. (This basic design should be part of the standard Core Data template...)
During the import process I save 2 different ways. Every 1000 items I save on the background context:
BOOL saveSuccess = [backgroundContext save:&error];
Then at the end of the import process, I save on the master/parent context which, ostensibly, pushes modifications out to the other child contexts including the main context:
[masterManagedObjectContext performBlock:^{
NSError *parentContextError = nil;
BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];
Problem: The problem is that my UI will not update until I reload the view.
I have a simple UIViewController with a UITableView that is being fed data using a NSFetchedResultsController. When the Import process completes, the NSFetchedResultsController see's no changes from the parent/master context and so the UI doesn't automatically update like I'm used to seeing. If I pop the UIViewController off the stack and load it again all the data is there.
Question: How do I get my child context to see changes persisted on the parent context so that they trigger my NSFetchedResultsController to update the UI?
I have tried the following which just hangs the app:
- (void)saveMasterContext {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:#selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
NSError *error = nil;
BOOL saveSuccess = [masterManagedObjectContext save:&error];
[notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
}
- (void)contextChanged:(NSNotification*)notification
{
if ([notification object] == mainManagedObjectContext) return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(contextChanged:) withObject:notification waitUntilDone:YES];
return;
}
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
You should probably save the master MOC in strides as well. No sense having that MOC wait until the end to save. It has its own thread, and it will help keep memory down as well.
You wrote:
Then at the end of the import process, I save on the master/parent
context which, ostensibly, pushes modifications out to the other child
contexts including the main context:
In your configuration, you have two children (the main MOC and the background MOC), both parented to the "master."
When you save on a child, it pushes the changes up into the parent. Other children of that MOC will see the data the next time they perform a fetch... they are not explicitly notified.
So, when BG saves, its data is pushed to MASTER. Note, however, that none of this data is on disk until MASTER saves. Furthermore, any new items will not get permanent IDs until the MASTER saves to disk.
In your scenario, you are pulling the data into the MAIN MOC by merging from the MASTER save during the DidSave notification.
That should work, so I'm curious as to where it is "hung." I will note, that you are not running on the main MOC thread in the canonical way (at least not for iOS 5).
Also, you probably only are interested in merging changes from the master MOC (though your registration looks like it is only for that anyway). If I were to use the update-on-did-save-notification, I'd do this...
- (void)contextChanged:(NSNotification*)notification {
// Only interested in merging from master into main.
if ([notification object] != masterManagedObjectContext) return;
[mainManagedObjectContext performBlock:^{
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
// NOTE: our MOC should not be updated, but we need to reload the data as well
}];
}
Now, for what may be your real issue regarding the hang... you show two different calls to save on the master. the first is well protected in its own performBlock, but the second is not (though you may be calling saveMasterContext in a performBlock...
However, I'd also change this code...
- (void)saveMasterContext {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:#selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
// Make sure the master runs in it's own thread...
[masterManagedObjectContext performBlock:^{
NSError *error = nil;
BOOL saveSuccess = [masterManagedObjectContext save:&error];
// Handle error...
[notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
}];
}
However, note that the MAIN is a child of MASTER. So, it should not have to merge the changes. Instead, just watch for the DidSave on the master, and just refetch! The data is sitting in your parent already, just waiting for you to ask for it. That's one of the benefits of having the data in the parent in the first place.
Another alternative to consider (and I'd be interested to hear about your results -- that's a lot of data)...
Instead of making the background MOC a child of the MASTER, make it a child of the MAIN.
Get this. Every time the BG saves, it automatically gets pushed into the MAIN. Now, the MAIN has to call save, and then the master has to call save, but all those are doing is moving pointers... until the master saves to disk.
The beauty of that method is that the data goes from the background MOC straight into your applications MOC (then passes through to get saved).
There is some penalty for the pass-through, but all the heavy lifting gets done in the MASTER when it hits the disk. And if you kick those saves on the master with performBlock, then main thread just sends off the request, and returns immediately.
Please let me know how it goes!

NSFetchRequest not seeing changes to relationships in NSManagedObjectContext

I am using a UIManagedDocument for core data. I have Calendar objects in my managedObjectContext that have a calendarSelected attribute. The Calendar entity has a to-many relationship to CalendarEntry.
When I change their calendarSelected attribute and then perform a NSFetchRequest to get CalendarEntry objects with the following predicate:
[NSPredicate predicateWithFormat:#"calendar.calendarSelected = YES"]
the calendar.calendarSelected does not seem to be seeing the change I made without me calling
[myManagedDocument saveToURL:myManagedDocument.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {}];
first. I read somewhere that fetching things from the same context should honor changes made to in that context even if the changes had not been written to the persistent store. What am I missing?
Update:
It appears to be happening when the calendarEvents relationships is a fault: calendarEvents = "<relationship fault: 0x91aec90 'calendarEvents'>"; but works when the relationship is not a fault.
If the issue occurs only on newly-inserted CalendarEntry objects pointed to by the to-many relationship, then it's possible that they don't yet have permanent object ids. This is easy to debug via just dumping the object id and checking to see if it's temporary or permanent.
I've seen this happen when the containing object in the to-many relationship is retained; it seems that so long as it is retained, the to-many contained objects in the relationship never get to a point wherein they obtain permanent ids. Somewhat easy to debug by putting the application in the background and restarting it; the backgrounding will typically force the UIManagedDocument to save, and things will start working as you expect thereafter, as the CalendarEntry entities will have been assigned permanent ids and thus will become fetchable.
So far as saving the UIManagedDocument, you don't have control over when that'll happen when using a UIManagedDocument. The best thing to do is schedule a save to occur in the future, via updateChangeCount:UIDocumentChangeDone, but again, it'll happen 'soon', but not deterministically, i.e., it's not possible to know when it'll happen.
To resolve the temporary vs. permanent object id issue, if that's what you're seeing, try the following category on NSManagedObjectContext; call it after you've completed inserting new CalendarEntry objects, in order to force the issue.
// Obtain permanent ids for any objects that have been inserted into
// this context.
//
// As of this writing (iOS 5.1), we need to manually obtain permanent
// ids for any inserted objects prior to a save being performed, or any
// objects that are currently retained, that have relationships to the
// inserted objects, won't be updated with permanent ids for the related
// objects when a save is performed unless the retained object is refetched.
// For many objects, that will never occur, so we need to force the issue.
- (BOOL)obtainPermanentIDsForInsertedObjects:(NSError **)error
{
NSSet * inserts = [self insertedObjects];
if ([inserts count] == 0) return YES;
return [self obtainPermanentIDsForObjects:[inserts allObjects]
error:error];
}
Basically, after making any changes, you must either use an undo manager, or call [document updateChangeCount:UIDocumentChangeDone]; and THAT'S ALL! Any other "save" calls will just break something else down the line somewhere.
The canonical method of updating should be...
You know you are on main thread
NSManagedObjectContext *ctx = document.managedObjectContext;
// Do stuff with it
[document updateChangeCount:UIDocumentChangeDone];
You are on a different thread
[document.managedObjectContext performBlock:^{
// Do stuff with it on the main thread
[document updateChangeCount:UIDocumentChangeDone];
}];
OR
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
moc.parentContext = document.managedObjectContext;
[moc performBlock:^{
// Do stuff with it on a background thread
// Push changes up to parent (which is the document's main MOC)
// The document will know it's dirty and save changes
[moc save:&error];
}];
OR if you want to make changes in a background thread, without messing with the main document MOC, do them on the parent... the main MOC will not see them until the next fetch.
[document.parentContext.managedObjectContext performBlock:^{
// Do stuff with it on a background thread, but the main MOC does not
// see the changes until it does a fetch.
}];

Multithreaded Core Data - NSManagedObject invalidated

As the title suggests im working with a Core Data Application which gets filled with objects in different background threads (XML Parsing)
In my background thread I'm doing this
managedContext = [[NSManagedObjectContext alloc] init];
[managedContext setUndoManager:nil];
[managedContext setPersistentStoreCoordinator: [[DataManager sharedManager] persistentStoreCoordinator]];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:managedContext];
NSMutableArray *returnSource = [[self parseDocument:doc] retain];
[managedContext save:&error];
if (error) {
NSLog(#"saving error in datafeed");
}
[managedContext reset];
[self performSelectorOnMainThread:#selector(parseCompleteWithSource:) withObject:returnSource waitUntilDone:YES];
The Merge method looks like this:
NSManagedObjectContext *mainContext = [[DataManager sharedManager] managedObjectContext];
// Merge changes into the main context on the main thread
[mainContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
[[NSNotificationCenter defaultCenter] removeObserver:self];
I think the merge is successful but as i want to display it in an UITableView it always tells me that my objects are invalidated which is to be expected because of
[managedContext reset];
What i want to do is show the Items which are currently in the database, in the background parse the xml and if thats finished i want to update the UITableView with the new / updated objects. How would I do that, can i "update" the objects to the other Context somehow or is the merge not working correctly?
Do I need to define something specific in the Main ObjectContext?
I've tried different mergepolicies without any luck.
Hope you can help me, thanks!
I believe your problem is the contents of the returnSource array. If that is a bunch of NSManagedObject instances then they will have been created on the background thread by the background thread context.
You call to -[NSManagedObjectContext reset] will invalidate them, since that is what you explicitly tell the context to do. But that is not the big problem.
You then go on to send the array to a the main thread, passing NSManagedObjectinstances over thread borders, and between contexts is a big no-no.
What you need to do is:
Create an array with the NSManagedObjectIDs of the NSManagedObject.
Send the object ID array over thread boundry.
Recreate an array with NSManagedObjects from the managed object IDs on the new thread with it's context.
I have made some Core Data helpers, following the rule of three (the third time you write something, make it general).
Most importantly I have hidden the complexity of managing different managed object contexts for each thread, handling notifications, and all that junk. Instead I have introduced the concept of thread local contexts. Basically lazily created NSManagedObjectContext instances that automatically registers for updates and cleanup when the current thread exits.
A normal use-case:
NSManagedObjectCotext* context = [NSManagedObjectCotext threadLocalContext];
// Do your stuff!!
NSError* error = nil;
if (![context saveWithFailureOption:NSManagedObjectContextCWSaveFailureOptionThreadDefault
error:&error])
{
// Handle error.
}
The full source code, including a sample app for parsing the news RSS from apple.com and store it in Core Data, is available here: https://github.com/jayway/CWCoreData.
There is no reason in this case to call reset on the background context because it will disappear anyway with the background thread and you never use it again in any case. You usually use reset with undo management when you want the context to forget previous steps.
Your major problem here is that your background thread is configured to receive notifications from the managed object context it creates. That is rather pointless.
Instead, you need to register the tableview controller (on the front thread) to receive the NSManagedObjectContextDidSaveNotification from the context on background thread. That way, when the background context saves, the front/main context will update itself with the new data which will trigger an update of the tableview.

Resources