cannot add parentContext to NSManagedObjectContext, context already has a coordinator - ios

I have a view where I retrieve a saved Entity (Route *) from the main NSManagedObjectContext. I want to import that into a tempContext. Following Marcus Zarra's examples, I do this:
NSManagedObjectContext *moc = _route.managedObjectContext;
NSManagedObjectID *routeId = [_route objectID];
NSPersistentStoreCoordinator *psc = moc.persistentStoreCoordinator;
self.tempContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[self.tempContext setPersistentStoreCoordinator:psc];
NSManagedObject *localRoute = [self.tempContext objectWithID:routeId];
[localRoute moToDictionary:localRoute];
self.tempContext.parentContext = moc; // crashes here
Everything is good until I try to set the parentContext of my tempContext to the main MOC. I get the error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Context already has a coordinator; cannot replace.'
I understand it's telling me that I cannot change the persistentStoreCoordinator. However I'm nto sure why it's telling me that. When I set a breakpoint, the tempContext is at a different memory addres than the main moc. Also, the self.tempContext.parentContext is nil. So I'd think if it's nil, I could set the nil parameter to the moc, but it crashes. Any thoughts? Thanks in advance!

For a managed object context, you can
either set the persistent store coordinator, to get a second independent MOC with the
same store,
or set the parent context to get a child MOC,
but not both.

Related

iOS/Objective-C: Save to core data in background thread

I am trying to follow this Apple example code for best practice saving to core data in background that includes this code:
NSArray *jsonArray = …; //JSON data to be imported into Core Data
NSManagedObjectContext *moc = …; //Our primary context on the main queue
NSManagedObjectContext *private = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[private setParentContext:moc];
My main MOC is save in a property. However, whether I alloc init a fresh MOC or use the one in the property,I get the error:
'Parent NSManagedObjectContext must use either NSPrivateQueueConcurrencyType or NSMainQueueConcurrencyType.'
*** First throw call stack:
The solution to this is said to specify the concurrency type for the MOC as follows:
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
Should this be done in the main core data stack? Or do you create a new MOC? I tried creating a new MOC and got an error that the MOC was null. It also seems redundant to create a second MOC that with the private one makes three. On the other hand I am afraid to change the main core data stack as it may throw other things off in the app.
What is best way to fix this?
The main MOC should be the child of the background private moc instead of the otherway around. Whenever you save the main moc, the private moc would get updated (thus you need to set the mergePolicy) and then saved to disk. In this scenario you don't need more than 2 mocs.
Because the save will be in the background thread, your code will run smoother in the main thread.
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; // primary context on the main queue
moc.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
NSManagedObjectContext *privateMoc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
privateMoc.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
[moc setParentContext:privateMoc];

Calling mergeChangesFromContextDidSaveNotification: Not Triggering mocObjectsDidChangeNotification in Main Context

This system uses the multi-threaded Core Data configuration of two contexts, shared persistent store.
I have an MOC created on the main thread of type NSMainQueueConcurrencyType (MOC A). There is an observer registered for NSManagedObjectContextObjectsDidChangeNotifications for MOC A.
Elsewhere, on a background thread in a queue, a background MOC (MOC B) of type NSPrivateQueueConcurrencyType and sharing the same PersistentStoreCoordinator as MOC A is created. After some operations, a save is called on MOC B.
An object is observing NSManagedObjectContextDidSaveNotifications on the background MOC B. Upon notification, it calls on the main thread a mergeChangesFromContextDidSaveNotification on MOC A.
Newly inserted and updated objects in MOC B do cause change notifications for observers on MOC A, but deletions in MOC B are not causing these change notifications. A fetch on MOC A does reflect that the deletions in MOC B were propagated to MOC A, but the issue is that there is no change notification. This is worrisome because views and controllers may think deleted objects are still valid.
Any guesses on why this could be happening?
Creation of MOC A
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];`
Creation of MOC B
NSPersistentStoreCoordinator *psc = [[NSManagedObject managedObjectContext] persistentStoreCoordinator];
dispatch_async(_backgroundQueue, ^{
// create second MOC
NSManagedObjectContext *bgMoc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
bgMoc.persistentStoreCoordinator = psc;
bgMoc.undoManager = nil;
[self registerForNotificationWithManagedObjectContext:bgMoc];
});
Notification Handling for NSManagedObjectContextDidSaveNotification
- (void)managedObjectContextObjectsDidSaveNotification:(NSNotification *)notification {
SEL selector = #selector(mergeChangesFromContextDidSaveNotification:);
[[NSManagedObject managedObjectContext] performSelectorOnMainThread:selector
withObject:notification
waitUntilDone:YES];
}
EDIT: I used the recommended SO answer for merging two MOCs from here: https://stackoverflow.com/a/6959868/1505750

Getting "NSObjectInaccessibleException" error when updating an nsmanagedobject in secondary thread

In the app I'm developing I need to update objects I store locally with any changes that have occurred with the server's version of these object. To perform the update check I alloc an NSOperation subclass with its own NSManagedObjectContext and parse any updates that I receive from the server before merging these back into the main NSManageObjectContext.
The issue I'm having is that when I'm presenting an NSManagedObject in the UI that is then updated via the secondary NSManagedObjectContext, the app crashes with an:
'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for... exception
I don't get this issue when the update is completed and the app is not showing the NSManagedObject in the UI
This is the method used to created NSOperation's local NSManagedObjectContext:
- (NSManagedObjectContext *) localManagedObjectContext
{
if (!_localManagedObjectContext)
{
_localManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[_localManagedObjectContext setParentContext:[CDAServiceManager managedObjectContext]];
[_localManagedObjectContext setUndoManager:nil];
[_localManagedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
}
return _localManagedObjectContext;
}
Is there a strategy for updating a currently being used NSManagedObject from a secondary NSManagedObjectContext?
call refresh on your MO so that changes show up
[ctx refreshObject:object mergeChanges:mergeChanges /*YES OR NO*/];

Core Data & GCD: Passing the correct managed object context to custom NSManagedObjects

I get runtime errors which seem to result from my incorrect implementation of GCD in combination with my custom NSManagedObjects.
Nested in a GCD call, I am using custom NSManagedObjects which (seem to) have their own managed object contexts (= self.managedObjectContext).
I am creating the managed object context in the app delegate by using the managed object context provided by UIManagedDocument: self.managedDocument.managedObjectContext.
I don't understand how to pass the correct managed object context down to my custom NSManagedObjects. How would I need to change my code to use the correct managed object context?
This is my main method (inside a view controller):
dispatch_queue_t queue;
queue = dispatch_queue_create("queue", NULL);
dispatch_async(queue, ^{
// ...
NSDecimalNumber *value = [reportedPeriod
valueForCoa:figure.code
convertedTo:self.currencySymbol];
// ...});
}
In this main method I do not have any reference to a managed object context, I do just call valueForCoa:convertedTo: (which is coded as follows):
- (NSDecimalNumber*)valueForCoa:(NSString*)coaStr
convertedTo:(NSString*)targetCurrencyStr {
// ...
CoaMap *coa = [CoaMap coaItemForString:coaStr
inManagedObjectContext:self.managedObjectContext];
// ...
}
valueForCoa is a method in my custom subclassed NSManagedObject ReportedPeriod and uses its (default) managed object context self.managedObjectContext.
The app then usually crashes in the custom subclassed NSManagedObject CoaMap in the following method when it executes the fetch request:
+ (CoaMap*)coaItemForString:(NSString*)coaStr
inManagedObjectContext:(NSManagedObjectContext*)context {
NSFetchRequest *request = [NSFetchRequest
fetchRequestWithEntityName:NSStringFromClass([self class])];
NSPredicate *predicate =
[NSPredicate predicateWithFormat:#"coa == %#",coaStr];
request.predicate = predicate;
// ** The runtime error occurs in the following line **
NSArray *results = [context executeFetchRequest:request error:nil];
// ...
}
The error message is: Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x9a8a4a0> was mutated while being enumerated.
Could you please help me with this issue and give me some suggestions on how to improve my code to pass the correct managed object contexts (or on how to make sure that the correct context is used in all methods)?
Thank you very much!
That error generally relates to using a managed object incorrectly context across different threads or queues. You created the MOC on the main queue, but you're using it on a background queue without considering that fact. It's not wrong to use the MOC on a background queue, but you need to be aware of that and take preparations.
You didn't say how you're creating the MOC. I suggest that you should be doing this:
NSManagedObjectContext *context = [[NSManagedObjectContext alloc]
initWithConcurrencyType: NSMainQueueConcurrencyType];
With main queue concurrency you can just use it normally on the main thread. When you're in your dispatch queue though, do this:
[context performBlockAndWait:^{
NSFetchRequest *request = [NSFetchRequest
fetchRequestWithEntityName:NSStringFromClass([self class])];
NSPredicate *predicate =
[NSPredicate predicateWithFormat:#"coa == %#",coaStr];
request.predicate = predicate;
NSArray *results = [context executeFetchRequest:request error:nil];
// ...
}];
This will ensure that the MOC's work occurs on the main thread even though you're on a background queue. (Technically what it actually means is that the MOC's work in the background will be correctly synchronized with work it does on the main queue, but the result is the same: this is the safe way to do this).
A similar approach would be to use NSPrivateQueueConcurrencyType instead. If you do that, you'd use performBlock or performBlockAndWait everywhere for the MOC, not just on background threads.
First,
"how to pass the correct managed object context down to my custom NSManagedObjects."
We create NSManagedObject with NSManagedObjectContext. not the other way around. So, when you have a NSManagedObject, you can access the NSManagedObjectContext by asking its property: – managedObjectContext as listed in Apple Document
Second,
When working with CoreData, multi-threading could be a little tricky. especially for the beginner. The are all kind of details that you need to take care of.
I highly recommend you checkout the Parent-Child NSManagedContext. then, using MagicRecord.
By using MagicRecord, you can simply Grand Central Dispatch with a Block like this:
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext){
// here use the `localContext` as your NSManagedContext and do things in the background.
// it will take care of all the rest.
}];
If you need to pass NSManagedObject into this block, remember to only pass the NSManagedObjectID instead of the proprety.
this is a example.
NSManagedObjectID *coaMapID = CoaMap.objectID;
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext){
coaMap *aCoaMap = (coaMap *)[localContext existingObjectWithID:coaMapID error:&error];
// then use aCoaMap normally.
}];
Hope this helped.

Core Data: nil managed object context on my managed object

I'm using 2 managed object contexts for efficiently important a large data set in the background. I'm ensuring I'm only using 1 managed object context at a time in the thread.
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:[self persistentStoreCoordinator]];
MetricType *metricType = [self metricTypeForName:self.metricName fromContext:context];
NSLog(#"metric context %#", [metricType managedObjectContext]);
[self processDataInContext:context forMetric:metricType];
In the snipped of code above, the NSLog correctly prints out the address of the managedObjectContext i'm using. I then go on to processDataInContext - which is just a private method to interate over a json data array and add objects. Each object has a relationship to the MetricType.
However, when I go to associate them
metric.metricType = metricType;
I get the error: Illegal attempt to establish a relationship 'metricType' between objects in different contexts.... even though I'm ensuring I don't do this.
When I do a log output before this line:
NSLog(#"My Context %#", context);
NSLog(#"metric context %#", [metricType managedObjectContext]);
The metricType context returns nil!!
How has it become nilled? I didn't nil it and this seems to be the reason its complaining.
I figured it out
[context reset];
..was being called every 50 records, which of course was removing my metricType object from the context and I wasn't re-fetching it.
The way you initialize metricType looks fishy. What does [self metricTypeForName:fromContext:] actually do to return a MetricType instance? Are you creating a new MetricType using [[MetricType alloc]init]? I suspect you are returning something in an auto-release pool

Resources