NSFetchedResultsController doesn't show updates from a different context - ios

I have an NSFetchedResultsController and a few operations updates managed objects on separate threads via NSOperationQueue.
The FRC (with its predicate) looks like this:
- (NSFetchedResultsController*)fetchedResultsController
{
if(fetchedResultsController) return fetchedResultsController;
NSManagedObjectContext* mainContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Check" inManagedObjectContext:mainContext]];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"isSync == %#", [NSNumber numberWithBool:NO]]];
[fetchRequest setFetchBatchSize:10];
fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:mainContext sectionNameKeyPath:nil cacheName:nil];
fetchedResultsController.delegate = self;
[fetchRequest release], fetchRequest = nil;
return fetchedResultsController;
}
The main thread and the threaded operation have their own managed object contexts. They only share the same coordinator.
Within the threaded operation I change the isSync property from NO to YES. To know what is Check entity to update, the main context passes to the threaded one a NSManagedObjectID.
The threaded operation retrieves the managed object like the following:
-(void)main
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSManagedObjectContext *exportContext = [[NSManagedObjectContext alloc] init];
[exportContext setPersistentStoreCoordinator:[self persistentStoreCoordinator]];
//...
Check* check = (Check*)[exportContext existingObjectWithID:objID error:&error];
check.isSync = [NSNumber numberWithBool:YES];
//...
[exportContext save:&error];
[pool release], pool = nil;
}
When the thread operation calls a save the mergeChangesFromContextDidSaveNotification notification is called and the main context merges the changes.
- (void)contextChanged:(NSNotification*)notification
{
if ([notification object] == [self managedObjectContext]) return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(contextChanged:) withObject:notification waitUntilDone:YES];
return;
}
[[self managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
}
Logging the description of the notification leads to verify that changes are performed correctly.
My problem
Delegates methods of NSFetchedResultsControllerDelegate are not called.
This is quite strange since dealing with the same context, the main one, allows to listen for changes and delegates methods are called, e.g. deleting a row object in the UITableView.
I've found some topics on SO with the same problem. I've tried all the workarounds but I cannot find a valuable solution:
NSFetchedResultsController not showing updates from other contexts
NSFetchedResultsController not firing delegate method after merging update from background thread
NSFetchedResultsController with predicate ignores changes merged from different NSManagedObjectContext
Thank you in advance.
Edit
The code above was working in a previous model. Then I created a new model copying (and pasting) entities from the previous one and now it doesn't work anymore.
Suggestions?
Edit 2
This is the predicate I'm using in NSFetchedResultsController getter. It's my fault, but when I wrote the post I didn't copy it.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"insertionDate" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
// previous code here
[fetchRequest setSortDescriptors:sortDescriptors];
Now, about Jody last comment
In the main() of your NSOperation, you are loading new objects, and in
there it looks like you are setting isSync to YES for each new object.
The predicate you use for the fetchedResultsController is looking only
for objects that have isSync == NO.
I expecting that when the property isSync is set to YES, the NSFetchedResultsController observes that changes and removes rows that not match the predicate. Am I wrong?
Remember that when merging changes from the background to the main thread, I'm able to see that few objects have updated their isSync property.

You have the basic idea, so there is probably a bug somewhere in your code...
Double check that you are properly registering to receive the notification from the background MOC.
Register to receive all notifications from all objects. In that method, log the event, and all its data. When the object is a MOC, dump all its properties (especially the lists of registered, inserted, updated, and deleted objects).
Put a log statement right before and after the save call, and in the notification handler for merging the notification.
Also, you omitted a lot of code so it's hard to know what you are actually doing, but the code sample you included looks like it is hard setting isSync to YES for all objects being loaded, but your fetch request only wants those with isSync set to NO. None of those new objects will pass that predicate.
Finally, double check your model definition and make sure you are using the right number type. This can be a big source of problems.
EDIT
Oh yeah, I forgot... your fetch request does not have a sort descriptor. When you create a FRC, your fetch request must contain at least one sort descriptor... if you have multiple sections, the first sort descriptor is used to group the objects into sections.
To follow up on Alexsander's comment... I alluded to it at the beginning of my post, but you certainly do not want to listen to notifications from a MOC unless it is well known as one of yours (unless, of course, you are just logging for debugging purposes). You should know about the MOC you are using.
Furthermore, I would suggest using parent/child MOCs for this type of processing, but what you are doing should work if done properly.
Parent (private concurrency type)
Main (main concurrency type)
Then, with your background MOCs, just have them set the main moc as their parent. When they save, their objects get injected directly into the main MOC. The main MOC can then issues saves at later times to put them onto disk.
Or, you can parent you background MOC to the "parent" and then the "main" MOC can just reissue the fetch to get the data from the parent.

I just had the same problem, which I solved with parent/child contexts.
Here is the problem I had.
I was updating my Core Data object graph in a background thread which had its own managedObjectContext (which is mandatory), and my fetchedResultsController was not able to pick up the changes done to the database.
After I solved it I took some notes:
ManagedObjectContexts are not thread safe, which means that a managedObjectContext cannot be shared with other threads. If a thread needs to use a managedObjectContext, then it will initialize its own managedObjectContext.
To initialize a managedObjectContext there are two ways:
alloc/init then set its persistentStoreCoordinator property
alloc/init then set a parentContext property instead of a persistentStoreCoordinator property
note: one cannot set both the persistentStoreCoordinator and the parentContext property of a managedObjectContext.
Using parent/child contexts is needed when the a context is run on a background thread that is "linked to controllers and UI objects that are required to be used only on the main thread"(core data documentation).
Here are the requirements needed for the parent/child contexts:
the parent context lives in the main thread
the child context lives in the background thread
both contexts need to be initialized with an initWithConcurrencyType:NSMainQueueConcurrencyType method.
when the batch of changes has been done in the child context, both contexts need to perform a save operation. These save operations need to be nested in performBlock methods, i.e:
childContext performBlock:^{
[childContext save:nil];
[self.parentContext performBlock:^{
[self.parentContext save:nil];
}];
}];
EDIT: The code above is actually a bad idea for 2 reasons:
1) It works without saving the parent context.
2) The main thread is blocked if the parent context runs on it.
I hope it helped !
EDIT: Here is a stackoverflow thread which helped me greatly: Does a Core Data parent ManagedObjectContext need to share a concurrency type with the child context?

Related

How to listen for specific entity saves/updates in CoreData

First thing I tried was to use a FetchedResultsController to solve my problem. This was an atypical use of FRC in that I was not updating a TableView, I was using it just to determine if entities were changing so I knew when to save to the server:
self.fetchedResultsController = [Document MR_fetchAllSortedBy:#"timestamp"
ascending:NO
withPredicate:[NSPredicate predicateWithFormat:#"report.remoteId != nil && dirty == YES"]
groupBy:nil
delegate:self
inContext:_managedObjectContext];
The issue here is that FRC does not receive updates when a relationship entity changes. IE if a report.remoteId goes from nil to non-nil I will not see the update as the FRC listens for changes on only the Document entity. Limitation outlined here: http://www.mlsite.net/blog/?p=825 and here Changing a managed object property doesn't trigger NSFetchedResultsController to update the table view
Not sure I really want to implement the workaround as I fell like I am using the FRC for things it was not meant to do. I don't think apple is going to fix this limitation, so I don't really want to smash a round peg into a square hole to solve my problem.
A couple of options
Fire a notification in code after I have saved entities, then listen for that elsewhere. Only thing I don't like about this is that it up to the programmer to do this and keep it up to date, ie if someone comes along and saves an entity in another place they must remember to fire the notification.
OR
Listen for saves to the default MOC. This is what I would really like to do. Something like this:
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(handleDataModelChange:)
name:NSManagedObjectContextObjectsDidChangeNotification
object:[NSManagedObjectContext MR_defaultContext]];
- (void)handleDataModelChange:(NSNotification *)note {
NSSet *updatedObjects = [[note userInfo] objectForKey:NSUpdatedObjectsKey];
NSSet *deletedObjects = [[note userInfo] objectForKey:NSDeletedObjectsKey];
NSSet *insertedObjects = [[note userInfo] objectForKey:NSInsertedObjectsKey];
// Do something in response to this
}
Here is my dilemma with this. This will listen to all changes on the default MOC. I really only care about changes to a couple of entities. So yeah I can filter out the entities I care about in each call. BUT, I have a case in which I save a ton of other entities in which I do not care about. This means that the NSManagedObjectContextObjectsDidChangeNotification will fire a ton and most of the time I will not care. I don't want to slow down my app by constantly receiving this notification and taking time to filter out all entities that I do not care about.
Is there a way to listen for specific entity saves? Either in CoreData, or MagicalRecord?
If the answer is no, is there a good alternative for listening to changes to a specific entity (and its relationship)?
There's no way to listen to changes to a specific set of entities; catching NSManagedObjectContextObjectsDidChangeNotification (or did or will save) and filtering is the correct approach, with the caveat that key-value observing is also an option if you're talking about specific instances of entities.
However, worth observing is that NSManagedObjectID is thread-safe and provides a getter for the NSEntityDescription. So you could e.g.
- (void)handleDataModelChange:(NSNotification *)note {
NSSet *updatedObjects = [[note userInfo] objectForKey:NSUpdatedObjectsKey];
NSSet *deletedObjects = [[note userInfo] objectForKey:NSDeletedObjectsKey];
NSSet *insertedObjects = [[note userInfo] objectForKey:NSInsertedObjectsKey];
NSMutableArray *objectIDs = [NSMutableArray array];
[objectIDs addObjectsFromArray:[updatedObjects.allObjects valueForKey:#"objectID"]];
[objectIDs addObjectsFromArray:[deletedObjects.allObjects valueForKey:#"objectID"]];
[objectIDs addObjectsFromArray:[insertedObjects.allObjects valueForKey:#"objectID"]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(){
NSSet *affectedEntities = [NSSet setWithArray:[objectIDs valueForKeyPath:#"entity.name"]];
if([affectedEntities containsObject:#"InterestingEntity"]) {
// get back onto the main thread and do some proper work;
// possibly having collated the relevant object IDs here
// first — whatever is appropriate
}
});
}
After reading through the docs and some other forms I also came up with another possible solution.
Why not override the -(void) didSave method of the managed object to catch a change on that object.
#implementation Report (helper)
- (void) didSave {
[super didSave];
// if not on default context no-op
if ([NSManagedObjectContext MR_defaultContext] != self.managedObjectContext) return;
// send custom notification here
}
#end
Still deals with sending a custom notification, but at least in encapsulated in the managed object, such that devs don't have to worry about where to send the notification.
On the plus side this only executes for the managed object that I care about.
Minus is that it is called for all managed object contexts. However I don't think the MOC check is going to be a heavy hitter.
Not sure if this is better/worse than listening on the default MOC for saves and filtering. Plus in that case is that I know I am listening for saves only on that MOC. Even though I can filter in a background task I am still filtering tons of data in that case that I don't need to deal with.
Thoughts?

refreshing an nsfetchedresultscontroller not tied to a uitableview

i am wrting an app that uses a number of NSFetchedResultsControllers (FRCs), each for a different Managed Object subclass in my data model. several of these FRCs are part of UITableViewControllers, but several are not. i have two questions: one, why is one of my FRCs not updating when i add a new object to the MOC, and two, should i be using an FRC at all for this purpose? i thought it would save me from having to put fetch requests in all over the code, but perhaps it only makes sense to use an FRC with a tableview or other such object.
so, here are the details.
one of the FRCs not tied to a tableview is one that keeps track of schedules. the SetScheduleViewController has the following properties (among others), which are passed in via the parent view controller:
#property (weak, nonatomic) NSFetchedResultsController *scheduleFetchedResultsController;
#property (weak, nonatomic) NSManagedObjectContext *managedObjectContext;
this FRC was created by another object (which maintains a strong pointer to it) via the following code
- (NSFetchedResultsController *)scheduleFetchedResultsController {
if (_scheduleFetchedResultsController != nil) {
return _scheduleFetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Schedule" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"start" ascending:NO];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *fRC = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
NSError *error = nil;
if (![fRC performFetch:&error]) {
IFLErrorHandler *errorHandler;
[errorHandler reportErrorToUser:error];
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
self.scheduleFetchedResultsController = fRC;
return self.scheduleFetchedResultsController;
}
(an aside: the reason the cacheName is set to nil is that i seem to be able to assign a cache name to only one of the many FRCs in this app, or the app crashes...if only one FRC is given a cache name and the rest have cache names set to nil, all seems well, though i am concerned that without a cache the performance may be terrible as the size of the persistent store grows...but that's another matter)
within the SetScheduleViewController, a schedule can be created or deleted, and on loading the SetScheduleViewController the most recently created schedule is loaded. when a schedule is created a new Schedule Managed Object is created, and then the MOC is saved as follows:
Schedule *newSchedule= [NSEntityDescription insertNewObjectForEntityForName:#"Schedule" inManagedObjectContext:self.managedObjectContext];
newSchedule.start=newStartTime;
//etc
NSError *saveError;
if (![self.managedObjectContext save:&saveError]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", saveError, [saveError userInfo]);
abort();
this successfully saves the new MO, but the FRC does not update...i've checked this several ways...for example, if after saving a new schedule i re-enter the SetScheduleViewController and check [[self.scheduleFetchedResultsController fetchedObjects] count] it is not incremented. however, if i quit the app and open it again, lo and behold, the FRC fetched object count is incremented and the new object is indeed there.
i should note that the scheduleFetchedResultsController does not have a delegate assigned, unlike the FRC's attached to tableviewcontrollers, which are all working fine. i didn't assign a delegate because from what i could tell, the FRC delegate methods in the tableviewcontrollers only deal with updating the tableview when the FRC changes content...i do not see any delegate methods that refresh the FRC itself when a new object is added and the MOC saved.
so, my two questions again are: 1) why is the FRC not refreshing (and how can i make it refresh), 2) does it even make sense to use an FRC to manage fetched results for a managed object not tied to a tableview, and should i instead simply perform a fresh fetch from the MOC every time i need access to the list of objects?
any help much appreciated.
In the NSFetchedResultsController documentation it is stated that the FRC is in "no-tracking" mode if no delegate has been set. Also, the delegate must implement at least one of the change tracking delegate methods in order for change tracking to be enabled.
The delegate does not have to be a table view controller, so you could use your
SetScheduleViewController as a delegate and just implement the controllerDidChangeContent: delegate method. Inside that method, the updated
fetchedObjects is available, and you can e.g. update any UI elements in the view
controller.
Update: Passing the FRC from the parentVC does not make much sense. Each view controller should have its own FRC. So scheduleFetchedResultsController should be a method in the childVC. And as the FRC is created "lazily" in the getter method, the getter has to be called somewhere.
In the case of table view controllers, this happens because all table view data source methods
access self.fetchedResultsController.
If your childVC does not access self.fetchedResultsController then the FRC
will not be created. That could be the reason why calling [self.fetchedResultsController performFetch:&error] in viewDidLoad, as suggested in the other answer, solved your problem.
The delegate method controllerDidChangeContent: will then be called if the result
set changes during the lifetime of the childVC. That's where using an FRC makes sense.
If you just want to fetch the current set of objects when the childVC is loaded then
you don't need a FRC, and you can just execute a simple fetch, e.g. in viewDidLoad.
I've faced the same problem before. The reason is you didn't performed the fetch of FRC.
Add the following code on -viewDidLoad:
NSError *error;
if (![self.fetchedResultsController performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}

NSFetchedResultsController is not showing all results after merging an `NSManagedObjectContextDidSaveNotification`

I have an NSFetchedResultsController which fetches objects with a predicate:
isTrash == NO
Most of the time this works as expected, but when an object gets untrashed the fetched results controller does not fetch the untrashed object.
What's going wrong?
The reason why this is happening is due to how mergeChangesFromContextDidSaveNotification: handles updated objects. NSManagedObjectContext keeps a record of objects which are in use in the context, these are referred to as registered objects (NSManagedObjectContext has methods for accessing and conditionally fetching registered objects). mergeChangesFromContextDidSaveNotification: only processes updates for objects which are registered in the context. This has a knock on effect for NSFetchedResultsControllers that explains the cause of the problem.
Here's how it plays out:
A FRC is setup with a predicate that doesn't match all objects (thus preventing the objects which do not match the predicate from being registered in the FRCs context).
A second context makes a change to an object which means that it now matches the FRCs predicate. The second context is saved.
The FRCs context processes the NSManagedObjectContextDidSaveNotification but only updates its registered objects, therefore it does not update the object which now matches the FRC predicate.
The FRC does not perform another fetch when there's a save, therefore isn't aware that the updated object should be included.
The fix
The solution is to fetch all updated objects when merging the notification. Here's an example merge method:
-(void)mergeChanges:(NSNotification *)notification {
dispatch_async(dispatch_get_main_queue, ^{
NSManagedObjectContext *savedContext = [notification object];
NSManagedObjectContext *mainContext = self.managedObjectContext;
BOOL isSelfSave = (savedContext == mainContext);
BOOL isSamePersistentStore = (savedContext.persistentStoreCoordinator == mainContext.persistentStoreCoordinator);
if (isSelfSave || !isSamePersistentStore) {
return;
}
[mainContext mergeChangesFromContextDidSaveNotification:notification];
//BUG FIX: When the notification is merged it only updates objects which are already registered in the context.
//If the predicate for a NSFetchedResultsController matches an updated object but the object is not registered
//in the FRC's context then the FRC will fail to include the updated object. The fix is to force all updated
//objects to be refreshed in the context thus making them available to the FRC.
//Note that we have to be very careful about which methods we call on the managed objects in the notifications userInfo.
for (NSManagedObject *unsafeManagedObject in notification.userInfo[NSUpdatedObjectsKey]) {
//Force the refresh of updated objects which may not have been registered in this context.
NSManagedObject *manangedObject = [mainContext existingObjectWithID:unsafeManagedObject.objectID error:NULL];
if (manangedObject != nil) {
[mainContext refreshObject:manangedObject mergeChanges:YES];
}
}
});
}
Try setting shouldRefreshRefetchedObjects to YES on the NSFetchRequest for your fetched results controller.
This provides more convenient way to ensure managed object property
values are consistent with the store than by using
refreshObject:mergeChanges: (NSManagedObjetContext) for multiple
objects in turn.

CoreData fetch request returns empty array

I have more of a conceptual question than code based because my code works.
When my app launches I fetch the sessionObject from coreData and validate the authToken.
This code works when in my loading controller. The Fetch request works and returns an array of sessionObjects. However in App Delegate where I validate the authToken the returned array is empty. Why does the code work in a controller but not in the App Delegate? There are no errors from the fetch request. The context is not nil. It's the exact code I use in the loading controller and that works.
Do I have to do requests differently for CoreData in the App Delegate? Can I use a fetch request in App Delegate?
Sample Code in the app Delegate DidBecomeActive method. I use DidBecomeActive so we can validate on return from background and init.
// check for valid authtoken if present so the correct home screen will display
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"CurrentSession" inManagedObjectContext:[self managedObjectContext]];
[request setEntity:entity];
CurrentSession *sessionObj = nil;
NSError *cdError = nil;
if([self managedObjectContext] == nil){
NSLog(#"context is nil");
}
NSArray *sessionArray = [[[self managedObjectContext] executeFetchRequest:request error:&cdError] mutableCopy];
if (sessionArray != nil && [sessionArray count]) {
sessionObj = (CurrentSession *)[sessionArray lastObject];
NSLog(#"Found session %#",sessionObj.authToken);
if (![sessionObj.authToken isEqualToString:#""]) {
[Solid_Utilities validateAuthToken:[self managedObjectContext]];
}
} else {
NSLog(#"NO SESSION FOUND");
}
EDIT
I'm sure my issue may be thread related.
In the loading controller I run a lot of tasks on separate threads and I assume App Delegate runs on main thread.
However the context I provide to the loading controller is generated in app delegate.
EDIT
I did a isMainThread check in App Delegate and Loading controller and both came back as true. Not sure why if they use the same context and store they wouldn't return the same array of objects.
Looks like the answer was related to another issue I had. Turns out the original developer added the core data stack Into the base controller and I was using the stack in the app delegate and passing it to the base controller. The base controller would overwrite my context with its own stack and this is why I couldn't get the fetch results expected in app delegate.
I would say from what I've learned over this project is to create the context in the app delegate and pass it to your first controller. Then in prepareForSegue or when you manually push pass the context along. Also in the view will disappear you check if your going back and update the stores context. If you do any multi threading then make sure in app delegate your context is nsmainconcurrencytype so you can create child context for other threads. This will keep data results as expected and conflicts to minimal.
Thanks for all the input on this. Your responses helped me track down the stupid issue.

Fetching Core Data objects in the background: objects not faulted

I need some help in using objects from Core Data with GCD; I seem to get NSManagedObjects that are aren't faulted into the main thread, even when I access their properties. Would appreciate some help.
This is what I'm doing: on launch, I need to load a list of Persons from the Core Data DB, do some custom processing in the background, then reload the table to show the names. I am following the guidelines for Core Data multi-threading by only passing in the objectIDs into the GCD queues. But when I reload the tableview on the main thread, I never see the name (or other properties) displayed for the contacts, and on closer inspection, the NSManagedObjects turn out to be faults on the main thread, even though I access various properties in cellForRowAtIndexPath. The name property is visible in the background thread when I NSLog it; and it's also showing correctly on the main thread in NSLogs in cellForRowAtIndexPath. But they don't show in the tableView no matter what I do. I tried accessing the name property using the dot notation, as well as valueForKey, but neither worked.
Here's my code …. it's called from the FRC initializer:
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil)
{
return __fetchedResultsController;
}
__fetchedResultsController = [self newFetchedResultsControllerWithSearch:nil]; // creates a new FRC
[self filterAllContactsIntoDictionary: __fetchedResultsController];
return [[__fetchedResultsController retain] autorelease];
}
- (void) filterAllContactsIntoDictionary: (NSFetchedResultsController *) frc
{
NSArray *fetchedIDs = [[frc fetchedObjects] valueForKey:#"objectID"];
NSArray *fetched = [frc fetchedObjects];
if (filterMainQueue == nil) {
filterMainQueue = dispatch_queue_create("com.queue.FilterMainQueue", NULL);
}
dispatch_async(self.filterMainQueue, ^{
NSManagedObjectContext *backgroundContext = [[[NSManagedObjectContext alloc] init] autorelease];
[backgroundContext setPersistentStoreCoordinator:[[self.fetchedResultsController managedObjectContext] persistentStoreCoordinator]];
NSMutableArray *backgroundObjects = [[NSMutableArray alloc] initWithCapacity: fetchedIDs.count];
// load the NSManagedObjects in this background context
for (NSManagedObjectID *personID in fetchedIDs)
{
Person *personInContext = (Person *) [backgroundContext objectWithID: personID];
[backgroundObjects addObject:personInContext];
}
[self internal_filterFetchedContacts: backgroundObjects]; // loads contacts into custom buckets
// done loading contacts into character buckets ... reload tableview on main thread before moving on
dispatch_async(dispatch_get_main_queue(), ^{
CGPoint savedOffset = [self.tableView contentOffset];
[self.tableView reloadData];
[self.tableView setContentOffset:savedOffset];
});
});
}
What am I doing wrong here? Is there any other way to explicitly make the Person objects fire their faults on the main thread? Or am I doing something wrong with GCD queues and Core Data that I'm not aware of?
Thanks.
Why not take the easy route, since you are not saving anything new ?
Instead of creating an extra context for the background thread and working with IDs, use the main managedObjectContext in the background thread after locking it.
for example:
- (void) filterAllContactsIntoDictionary: (NSFetchedResultsController *) frc
{
if (filterMainQueue == nil) {
filterMainQueue = dispatch_queue_create("com.queue.FilterMainQueue", NULL);
}
dispatch_async(self.filterMainQueue, ^{
NSManagedObjectContext *context = ... // get the main context.
[context lock]; // lock the context.
// do something with the context as if it were on the main thread.
[context unlock]; // unlock the context.
dispatch_async(dispatch_get_main_queue(), ^{
CGPoint savedOffset = [self.tableView contentOffset];
[self.tableView reloadData];
[self.tableView setContentOffset:savedOffset];
});
});
}
This works for me when I call a method with performSelectorInBackground, so I guess it should work for GCD dispatch too.
Well, mergeChangesFromContextDidSaveNotification: is your friend. You'll need to tell the MOC on the main thread that there have been changes elsewhere. This will do the trick.
Here's Apple's documentation. To quote from there:
This method refreshes any objects which have been updated in the other context, faults in any newly-inserted objects, and invokes deleteObject:: on those which have been deleted.
EDIT: original answer removed, OP is not fetching in the background
I looked closer at your code and it doesn't look like you are doing anything that will change data and/or affect the context on the main thread.
You have a fetchedResultsController on the main thread. Presumably, this is working and your table is populating with data. Is this true?
When filterAllContentsIntoDictionary is invoked, you pass an array of the fetchedResultsController's current objectIDs to a background thread and do some processing on them (presumably filtering them based on some criteria) but you are not changing data and saving backgroundContext.
internalFilterFetchedContents is a black box. Without knowing what you intend for it to do, hard to say why it's not working.
When this is done, you reload the table on the main thread.
You haven't made any changes to the store, the context, or the fetchedResultsController so of course, the table shows the same data it did before. The missing details to help further are:
Is your tableView showing correct data from the fetchedResultsController to begin with? If not, most likely your only problem is in handling the tableView delegate and dataSource methods and the rest of this isn't really relevant.
What do you intend to happen in filterAllContentsIntoDictionary and internalFilterFetchedContents?
If your intent is to filter the data as displayed by the fetchedResultsController not sure you need to do anything in the background. If you modify the fetchRequest and do performFetch again your table will reload based on the new results.
I you need more help, please answer my questions, add more relevant code to your post and let me know if I'm missing anything wrt the problem and what you're trying to accomplish.
good luck!

Resources