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*/];
Related
I'm having problems with Core Data concurrency on my iOS app.
On my executeFetchRequest I tried to synchronize the managedObjectContext request, but some times this method makes my app freeze.
- (NSArray *)synchronizedWithFetchRequest:(NSFetchRequest *)request andError:(NSError **)error
{
#synchronized(self.managedObjectContext)
{
return [self.managedObjectContext executeFetchRequest:request error:error];
}
}
I've already tried many things like lock/unlock, performBlock/performBlockAndWait, dispatch_sync/dispatch_async and nothing seems to work.
Managed Object Context creation:
...
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
Is there some way around this? and keep my request returning the results objects on this method?
Thanks!
Synchronising on the MOC suggests that there is more than one thread accessing the same MOC.
That in itself is a violation of CoreData concurrency protocols.
This access is prohibited unless it is wrapped in the context performBlock: method (or its "wait" counterpart). this will negate the need for the #synchronized block altogether.
This thread/queue "boundness" extends to the contexts fetched/registered managed objects, and so, you will not be able to access them as the return values of your method.
I am using XMPP Framework's Core Data message storage and I want to add to my app another Core Data Entity, I create .xcdatamodeld and create Entity, I add code to my AppDelegate, but I get error. Can it be because I use XMPP Framework's Core Data message storage and how to fix it?
NSManagedObjectContext *context = [self managedObjectContext_messageList];
NSManagedObject *contexNew = [NSEntityDescription insertNewObjectForEntityForName:#"UserProfileEntity" inManagedObjectContext:context];
[contexNew setValue:#1 forKey:#"id"];
[contexNew setValue:#"name TEST" forKey:#"name"];
[contexNew setValue:#"test YEP" forKey:#"test"];
NSError *error;
if ([context save:&error])
{
NSLog(#"%#", error.description);
}
It crashes on on second line with
'+entityForName: nil is not a legal NSManagedObjectContext parameter
searching for entity name 'UserProfileEntity''
That error means that nil is not a legal thing to pass for the managed object context. So, did you inject the context in the correct manner? I mean that the following line
NSManagedObjectContext *context = [self managedObjectContext_messageList];
should have a counterpart like
yourController.managedObjectContext_messageList = theContextYouWantToInject;
or
[yourController setManagedObjectContext_messageList:theContextYouWantToInject];
If you are using segues here a useful discussion: '+entityForName: nil is not a legal NSManagedObjectContext parameter - Core Data.
In addition, are you sure you are providing a valid store coordinator? For further references take a look to The Core Data Stack.
I am currently developing an application that uses Core Data to store data. The application synchronizes its content with a web server by downloading and parsing a huge XML file (about 40000 entries). The application allows the user to search data and modify it (CRUD). The fetch operations are too heavy, that is why i decided to use the following pattern :
"One managed object context for the main thread (NSMainQueueConcurrencyType) in order to refresh user interface. The heavy fetching and updates are done through multiple background managed object contexts (NSPrivateQueueConcurrencyType). No use of children contexts".
I fetch some objects into an array (let us say array of "users"), then i try to update or delete one "user" (the object "user" is obtained from the populated array)in a background context and finally i save that context.
I am listening to NSManagedObjectContextDidSaveNotification and merge any modifications with my main thread managed object context.
Every thing works fine except when i relaunch my application i realize that none of the modifications has been saved.
Here is some code to explain the used pattern
Main managed object context :
-(NSManagedObjectContext *)mainManagedObjectContext {
if (_mainManagedObjectContext != nil)
{
return _mainManagedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
_mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainManagedObjectContext setPersistentStoreCoordinator:coordinator];
return _mainManagedObjectContext;
}
Background managed object context :
-(NSManagedObjectContext *)newManagedObjectContext {
NSManagedObjectContext *newContext;
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
newContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[newContext performBlockAndWait:^{
[newContext setPersistentStoreCoordinator:coordinator];
}];
return newContext;
}
Update a record :
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
FootBallCoach *coach = [_coaches objectAtIndex:indexPath.row];
coach.firstName = [NSString stringWithFormat:#"Coach %i",indexPath.row];
NSManagedObjectContext *context = [[SDCoreDataController sharedInstance] newManagedObjectContext];
[context performBlock:^{
NSError *error;
[context save:&error];
if (error)
{
NSLog(#"ERROR SAVING : %#",error.localizedDescription);
}
dispatch_async(dispatch_get_main_queue(), ^{
[self refreshCoaches:nil];
});
}];
}
Am i missing any thing ? should i save my main managed object context after saving the background context ?
If your context is configured with a persistent store coordinator, then save should write data to the store. If your context is configured with another context as parent, then save will push the data to the parent. Only when the last parent, the one that is configured with persistent store coordinator is saved, is the data written to the store.
Check that your background context is really configured with persistent store coordinator.
Check the return value and possible error of the -save:.
Make sure you work with your background context via -performBlock...: methods.
UPDATE
Each time you call your -newManagedObjectContext method, a new context is created. This context knows nothing about FootBallCoach object you’re updating. You need to save the same context FootBallCoach object belongs to.
Don’t forget that each object belongs to one and only one context.
Also make sure you hold a strong reference to a context whose objects you’re using.
I'm still coding my RSS reader and I have gotten to the point where I'd like things to go smoother by background filling my Feeds at once with the newest Posts.
The problem is that it crashes my app quite badly with messages such as:
2013-10-02 21:06:25.474 uRSS[97209:a0b] *** Terminating app due to uncaught
exception 'NSInternalInconsistencyException', reason: 'UITableView dataSource
must return a cell from tableView:cellForRowAtIndexPath:'
(stacktrace)
I came to the conclusion that I am not running thread safe here and I then discovered this kind of CoreData snippets:
//Core Data's NSPrivateQueueConcurrencyType and sharing objects between threads
[context performBlock:^{
// fetch request code
NSArray *results = [context executeFetchRequest:request error:nil];
dispatch_async(dispatch_get_main_queue(), ^(void) {
Class *firstObject = [results objectAtIndex:0];
// do something with firstObject
});
}];
// Assume we have these two context (They need to be set up. Assume they are.)
NSManagedObjectContext *mainMOC = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType] autorelease];
NSManagedObjectContext *backgroundMOC = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType] autorelease];
// Now this can safely be called from ANY thread:
[backgroundMOC performBlock:^{
NSArray *results = [context executeFetchRequest:request error:nil];
for (NSManagedObject *mo in results) {
NSManagedObjectID *moid = [mo objectID];
[mainMOC performBlock:^{
NSManagedObject *mainMO = [mainMOC objectWithID:moid];
// Do stuff with 'mainMO'. Be careful NOT to use 'mo'.
}];
}
}];
Now, what I would like to know is the following:
should the backgroundMOC be defined as a Class member property, or everytime the method that uses it is invoked?
what if this method is itself invoked asynchronously (the RSS parsing method create the objects on the fly)?
How may I securely notify my UITAbleView that my MOC's been updated so that it can refresh without crashing?
Does this only apply to fetches, or also to objects insertions, deletions, etc?
Where could I find a working example of this concept successfully applied?
1) backgroundMOC should be defined in the scope, where you use it. Say, if you use context inside of SomeClass, it's good to define it as property of SomeClass. However, usually many classes share same context (for example, it's quite OK to share mainMOC between all your viewControllers) so i suggest to define mainMOC and backgroundMOC in your AppDelegate or some other singleton.
2) It's OK. However, it's bad idea to create contexts every time — see 1 and initialize them once in singleton.
3) Take a look at NSFetchedResultsController. It's exactly what you need to setup your tableView and track CoreData changes.
4) Yes
5) Cannot really point you to working example. Find something out on developer.apple.com =)
Also remarks:
1) Your class cannot be named Class
2) Use existingObjectWithID:error:, not objectWithID: — check this answer, it was really annoying issue in my experience
3) Read about NSManagedObjectContext concurrency patterns
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.