How to properly save updates with MagicalRecord? - ios

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

Related

Saving context for every action

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.

Core Data background context best practice

I have a large import task I need to do with core data.
Let say my core data model look like this:
Car
----
identifier
type
I fetch a list of car info JSON from my server and then I want to sync it with my core data Car object, meaning:
If its a new car -> create a new Core Data Car object from the new info.
If the car already exists -> update the Core Data Car object.
So I want to do this import in background without blocking the UI and while the use scrolls a cars table view that present all the cars.
Currently I'm doing something like this:
// create background context
NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[bgContext setParentContext:self.mainContext];
[bgContext performBlock:^{
NSArray *newCarsInfo = [self fetchNewCarInfoFromServer];
// import the new data to Core Data...
// I'm trying to do an efficient import here,
// with few fetches as I can, and in batches
for (... num of batches ...) {
// do batch import...
// save bg context in the end of each batch
[bgContext save:&error];
}
// when all import batches are over I call save on the main context
// save
NSError *error = nil;
[self.mainContext save:&error];
}];
But I'm not really sure I'm doing the right thing here, for example:
Is it ok that I use setParentContext ?
I saw some examples that use it like this, but I saw other examples that don't call setParentContext, instead they do something like this:
NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
bgContext.persistentStoreCoordinator = self.mainContext.persistentStoreCoordinator;
bgContext.undoManager = nil;
Another thing that I'm not sure is when to call save on the main context, In my example I just call save in the end of the import, but I saw examples that uses:
[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification* note) {
NSManagedObjectContext *moc = self.managedObjectContext;
if (note.object != moc) {
[moc performBlock:^(){
[moc mergeChangesFromContextDidSaveNotification:note];
}];
}
}];
As I mention before, I want the user to be able to interact with the data while updating, so what if I the user change a car type while the import change the same car, is the way I wrote it safe?
UPDATE:
Thanks to #TheBasicMind great explanation I'm trying to implement option A, so my code looks something like:
This is the Core Data configuration in AppDelegate:
AppDelegate.m
#pragma mark - Core Data stack
- (void)saveContext {
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
DDLogError(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
}
// main
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_managedObjectContext.parentContext = [self saveManagedObjectContext];
return _managedObjectContext;
}
// save context, parent of main context
- (NSManagedObjectContext *)saveManagedObjectContext {
if (_writerManagedObjectContext != nil) {
return _writerManagedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_writerManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_writerManagedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _writerManagedObjectContext;
}
And this is how my import method looks like now:
- (void)import {
NSManagedObjectContext *saveObjectContext = [AppDelegate saveManagedObjectContext];
// create background context
NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType];
bgContext.parentContext = saveObjectContext;
[bgContext performBlock:^{
NSArray *newCarsInfo = [self fetchNewCarInfoFromServer];
// import the new data to Core Data...
// I'm trying to do an efficient import here,
// with few fetches as I can, and in batches
for (... num of batches ...) {
// do batch import...
// save bg context in the end of each batch
[bgContext save:&error];
}
// no call here for main save...
// instead use NSManagedObjectContextDidSaveNotification to merge changes
}];
}
And I also have the following observer:
[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification* note) {
NSManagedObjectContext *mainContext = self.managedObjectContext;
NSManagedObjectContext *otherMoc = note.object;
if (otherMoc.persistentStoreCoordinator == mainContext.persistentStoreCoordinator) {
if (otherMoc != mainContext) {
[mainContext performBlock:^(){
[mainContext mergeChangesFromContextDidSaveNotification:note];
}];
}
}
}];
This is an extremely confusing topic for people approaching Core Data for the first time. I don't say this lightly, but with experience, I am confident in saying the Apple documentation is somewhat misleading on this matter (it is in fact consistent if you read it very carefully, but they don't adequately illustrate why merging data remains in many instances a better solution than relying on parent/child contexts and simply saving from a child to the parent).
The documentation gives the strong impression parent/child contexts are the new preferred way to do background processing. However Apple neglect to highlight some strong caveats. Firstly, be aware that everything you fetch into your child context is first pulled through it's parent. Therefore it is best to limit any child of the main context running on the main thread to processing (editing) data that has already been presented in the UI on the main thread. If you use it for general synchronisation tasks it is likely you will be wanting to process data which extends far beyond the bounds of what you are currently displaying in the UI. Even if you use NSPrivateQueueConcurrencyType, for the child edit context, you will potentially be dragging a large amount of data through the main context and that can lead to bad performance and blocking. Now it is best not to make the main context a child of the context you use for synchronisation, because it won't be notified of synchronisation updates unless you are going to do that manually, plus you will be executing potentially long running tasks on a context you might need to be responsive to saves initiated as a cascade from the edit context that is a child of your main context, through the main contact and down to the data store. You will have to either manually merge the data and also possibly track what needs to be invalidated in the main context and re-sync. Not the easiest pattern.
What the Apple documentation does not make clear is that you are most likely to need a hybrid of the techniques described on the pages describing the "old" thread confinement way of doing things, and the new Parent-Child contexts way of doing things.
Your best bet is probably (and I'm giving a generic solution here, the best solution may be dependent on your detailed requirements), to have a NSPrivateQueueConcurrencyType save context as the topmost parent, which saves directly to the datastore. [Edit: you won't be doing very much directly on this context], then give that save context at least two direct children. One your NSMainQueueConcurrencyType main context you use for the UI [Edit: it's best to be disciplined and avoid ever doing any editing of the data on this context], the other a NSPrivateQueueConcurrencyType, you use to do user edits of the data and also (in option A in the attached diagram) your synchronisation tasks.
Then you make the main context the target of the NSManagedObjectContextDidSave notification generated by the sync context, and send the notifications .userInfo dictionary to the main context's mergeChangesFromContextDidSaveNotification:.
The next question to consider is where you put the user edit context (the context where edits made by the user get reflected back into the interface). If the user's actions are always confined to edits on small amounts of presented data, then making this a child of the main context again using the NSPrivateQueueConcurrencyType is your best bet and easiest to manage (save will then save edits directly into the main context and if you have an NSFetchedResultsController, the appropriate delegate method will be called automatically so your UI can process the updates controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:) (again this is option A).
If on the other hand user actions might result in large amounts of data being processed, you might want to consider making it another peer of the main context and the sync context, such that the save context has three direct children. main, sync (private queue type) and edit (private queue type). I've shown this arrangement as option B on the diagram.
Similarly to the sync context you will need to [Edit: configure the main context to receive notifications] when data is saved (or if you need more granularity, when data is updated) and take action to merge the data in (typically using mergeChangesFromContextDidSaveNotification:). Note that with this arrangement, there is no need for the main context to ever call the save: method.
To understand parent/child relationships, take Option A: The parent child approach simply means if the edit context fetches NSManagedObjects, they will be "copied into" (registered with) first the save context, then the main context, then finally edit context. You will be able to make changes to them, then when you call save: on the edit context, the changes will saved just to the main context. You would have to call save: on the main context and then call save: on the save context before they will be written out to disk.
When you save from a child, up to a parent, the various NSManagedObject change and save notifications are fired. So for example if you are using a fetch results controller to manage your data for your UI, then it's delegate methods will be called so you can update the UI as appropriate.
Some consequences: If you fetch object and NSManagedObject A on the edit context, then modify it, and save, so the modifications are returned to the main context. You now have the modified object registered against both the main and the edit context. It would be bad style to do so, but you could now modify the object again on the main context and it will now be different from the object as it is stored in the edit context. If you then try to make further modifications to the object as stored in the edit context, your modifications will be out of sync with the object on the main context, and any attempt to save the edit context will raise an error.
For this reason, with an arrangement like option A, it is a good pattern to try to fetch objects, modify them, save them and reset the edit context (e.g. [editContext reset] with any single iteration of the run-loop (or within any given block passed to [editContext performBlock:]). It is also best to be disciplined and avoid ever doing any edits on the main context.
Also, to re-iterate, since all processing on main is the main thread, if you fetch lots of objects to the edit context, the main context will be doing it's fetch processing on the main thread as those objects are being copied down iteratively from parent to child contexts. If there is a lot of data being processed, this can cause unresponsiveness in the UI. So if, for example you have a large store of managed objects, and you have a UI option that would result in them all being edited. It would be a bad idea in this case to configure your App like option A. In such a case option B is a better bet.
If you aren't processing thousands of objects, then option A may be entirely sufficient.
BTW don't worry too much over which option you select. It might be a good idea to start with A and if you need to change to B. It's easier than you might think to make such a change and usually has fewer consequences than you might expect.
Firstly, parent/child context are not for background processing. They are for atomic updates of related data that might be created in multiple view controllers. So if the last view controller is cancelled, the child context can be thrown away with no adverse affects on the parent. This is fully explained by Apple at the bottom of this answer at [^1]. Now that is out of the way and you haven't fallen for the common mistake, you can focus on how to properly do background Core Data.
Create a new persistent store coordinator (no longer needed on iOS 10 see update below) and a private queue context. Listen for the save notification and merge the changes into the main context (on iOS 10 the context has a property to do this automatically)
For a sample by Apple see "Earthquakes: Populating a Core Data Store Using a Background Queue"
https://developer.apple.com/library/mac/samplecode/Earthquakes/Introduction/Intro.html
As you can see from the revision history on 2014-08-19 they added
"New sample code that shows how to use a second Core Data stack to fetch data on a background queue."
Here is that bit from AAPLCoreDataStackManager.m:
// Creates a new Core Data stack and returns a managed object context associated with a private queue.
- (NSManagedObjectContext *)createPrivateQueueContext:(NSError * __autoreleasing *)error {
// It uses the same store and model, but a new persistent store coordinator and context.
NSPersistentStoreCoordinator *localCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[AAPLCoreDataStackManager sharedManager].managedObjectModel];
if (![localCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil
URL:[AAPLCoreDataStackManager sharedManager].storeURL
options:nil
error:error]) {
return nil;
}
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[context performBlockAndWait:^{
[context setPersistentStoreCoordinator:localCoordinator];
// Avoid using default merge policy in multi-threading environment:
// when we delete (and save) a record in one context,
// and try to save edits on the same record in the other context before merging the changes,
// an exception will be thrown because Core Data by default uses NSErrorMergePolicy.
// Setting a reasonable mergePolicy is a good practice to avoid that kind of exception.
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
// In OS X, a context provides an undo manager by default
// Disable it for performance benefit
context.undoManager = nil;
}];
return context;
}
And in AAPLQuakesViewController.m
- (void)contextDidSaveNotificationHandler:(NSNotification *)notification {
if (notification.object != self.managedObjectContext) {
[self.managedObjectContext performBlock:^{
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}];
}
}
Here is the full description of how the sample is designed:
Earthquakes: Using a "private" persistent store coordinator to fetch data in background
Most applications that use Core Data employ a single persistent store coordinator to mediate access to a given persistent store. Earthquakes shows how to use an additional "private" persistent store coordinator when creating managed objects using data retrieved from a remote server.
Application Architecture
The application uses two Core Data "stacks" (as defined by the existence of a persistent store coordinator). The first is the typical "general purpose" stack; the second is created by a view controller specifically to fetch data from a remote server (As of iOS 10 a second coordinator is no longer needed, see update at bottom of answer).
The main persistent store coordinator is vended by a singleton "stack controller" object (an instance of CoreDataStackManager). It is the responsibility of its clients to create a managed object context to work with the coordinator[^1]. The stack controller also vends properties for the managed object model used by the application, and the location of the persistent store. Clients can use these latter properties to set up additional persistent store coordinators to work in parallel with the main coordinator.
The main view controller, an instance of QuakesViewController, uses the stack controller's persistent store coordinator to fetch quakes from the persistent store to display in a table view. Retrieving data from the server can be a long-running operation which requires significant interaction with the persistent store to determine whether records retrieved from the server are new quakes or potential updates to existing quakes. To ensure that the application can remain responsive during this operation, the view controller employs a second coordinator to manage interaction with the persistent store. It configures the coordinator to use the same managed object model and persistent store as the main coordinator vended by the stack controller. It creates a managed object context bound to a private queue to fetch data from the store and commit changes to the store.
[^1]: This supports the "pass the baton" approach whereby—particularly in iOS applications—a context is passed from one view controller to another. The root view controller is responsible for creating the initial context, and passing it to child view controllers as/when necessary.
The reason for this pattern is to ensure that changes to the managed object graph are appropriately constrained. Core Data supports "nested" managed object contexts which allow for a flexible architecture that make it easy to support independent, cancellable, change sets. With a child context, you can allow the user to make a set of changes to managed objects that can then either be committed wholesale to the parent (and ultimately saved to the store) as a single transaction, or discarded. If all parts of the application simply retrieve the same context from, say, an application delegate, it makes this behavior difficult or impossible to support.
Update: In iOS 10 Apple moved synchronisation from the sqlite file level up to the persistent coordinator. This means you can now create a private queue context and reuse the existing coordinator used by the main context without the same performance problems you would have had doing it that way before, cool!
By the way this document of Apple is explaining this problem very clearly. Swift version of above for anyone interested
let jsonArray = … //JSON data to be imported into Core Data
let moc = … //Our primary context on the main queue
let privateMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateMOC.parentContext = moc
privateMOC.performBlock {
for jsonObject in jsonArray {
let mo = … //Managed object that matches the incoming JSON structure
//update MO with data from the dictionary
}
do {
try privateMOC.save()
moc.performBlockAndWait {
do {
try moc.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
} catch {
fatalError("Failure to save context: \(error)")
}
}
And even simpler if you are using NSPersistentContainer for iOS 10 and above
let jsonArray = …
let container = self.persistentContainer
container.performBackgroundTask() { (context) in
for jsonObject in jsonArray {
let mo = CarMO(context: context)
mo.populateFromJSON(jsonObject)
}
do {
try context.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}

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

Model object and Core Data

I have an object that I load up data from Core Data. I then modify the object with user inputs/choices.
My first though was to override the setter methods for the properties:
-(void)setType:(NSString *)type {
NSLog(#"setType fired | newType: %#", type);
_type = type;
appDelegate *appDelegate = (appDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:DEFAULTS_DB];
NSError *error;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects.count == 1) {
Defaults *defaults = [fetchedObjects objectAtIndex:0];
defaults.sceneType = type;
}
if (![context save:&error]) {
NSLog(#"Error in saving first run defaults | error: %#", error);
}
else {
NSLog(#"new type was saved to core data");
}
}
My other thought was to update the Core Data when applicationWillResignActive: fires (but that method only gets a few seconds to run before the app is frozen) and when the user logs out.
The app i'm creating is one that the user would start up, set up what he wants, then puts it down 10-60min until he uses it again so i'm concerned with my app being killed while inactive and loosing the data.
Is updating core data in the setter method a good way to handle updates or is it a really bad idea (too resource intensive, too slow)?
It's not a typical usage pattern. Typical would be to fetch your object before displaying its data and hold it. Then when the user changes something, update object properties and save the context.
It sort of depends on how much amount of data you are setting and retrieving with each call to your customer setters. I'd try what you are doing now (updating core data in setters) but test it out on an old device, say an iPad 1, and see how it performs. After playing it out for a while, you can decide whether you want to update core data with every setter or not.
Now, if you don't see any performance difference at all, I wouldn't worry about it. You're good. You are having the comfort of being able to save the most updated actions from the user by committing your data in setters. However, there might come a point when you are saving more data and/or large files/images and the earlier devices start to see a difference in performance.
In order to be prepared for such a situation in advance, I'd suggest having a buffer (that mirrors the structure of your entity) in your program that gets written to core data every so often - may be when the app goes into a state of inactivity or if the app goes into background or for every fixed time period. It's imperative the buffer gets updated in the setters. The possible hiccup with your idea of writing code in applicationWillResignActive: stems from how prepared you are at handling crashes. Crashes can happen any time for any reason, sometimes maybe not because of your own bad code. And this is one strong reason why having buffers is a good idea. You either lose one chunk of small data during a crash (worst case) or you achieve performance + consistency (best case).

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

Resources