views:

251

answers:

3

I have a table view that is managed by an NSFetchedResultsController. I am having an issue with a find-or-create operation, however. When the user hits the bottom of my table view, I am querying my server for another batch of content. If it doesn't exist in the local cache, we create it and store it. If it does exist, however, I want to append that data to the fetched results controller and display it. I can't quite figure that part out.

Here's what I'm doing thus far:

  1. The NSFetchedRequestController when initialized queries for the latest 100 results from the database (using setFetchLimit:). Even if there are 1000 rows, I only want 100 accessible at first.
  2. Passing the returned array of values from my server to an NSOperation to process.
  3. In the operation, create a new managed object context to work with.
  4. In the operation, I iterate through the array and execute a fetch request to see if the object exists (based on its server id).
  5. If the object doesn't exist, we create it and insert it into the operations' managed object context.
  6. After the iteration completes, we save the managed object context, which triggers a merge notification on my main thread.

During the merge, the newly created objects from step 4 are inserted into the table, but any object that already existed and was just fetched does not. Here's the relevant code from my NSOperation

for (NSDictionary *resultsDict in self.results)
{
    NSNumber *dbID = [NSNumber numberWithLong:[[resultsDict valueForKey:@"id"] longValue]];

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    [fetchRequest setEntity:[NSEntityDescription entityForName:kResultEntityName inManagedObjectContext:moc]];
    [fetchRequest setPredicate:[NSPredicate predicateWithFormat: @"(dbID == %@)", dbID]];

    NSError *error = nil;

    NSManagedObject *newObject = nil;
    // Query the data store to see if the object exists
    newObject = [[moc executeFetchRequest:fetchRequest error:&error] lastObject];
    [fetchRequest release];

    // If it doesn't exist, create it.
    if ((tweet == nil))
    {
        // Create the NSManagedObject and insert it into the MOC.  
    }
}

What I want to pass to my main thread is the newly created objects plus any existing objects that may have been fetched in the fetch request each time the loop iterates.

I feel like it's something simple I'm missing, and could use a nudge in the right direction.

+1  A: 

At this point, any objects that weren't locally cached in my Core Data store before will appear, but the ones that previously existed do not come along for the ride.

Can you explain this a little? I am not sure what you mean by the ones that previously existed. Is it objects that didn't match the filter on your table that now do after the retrieval or are you saying that the previous objects disappear from the table?

Also, how are you implementing the delegate methods for the NSFetchedResultsController? Are you doing a simple table reload or are you inserting/moving rows?

Update

There are a couple of ways to do this but they would require some experimentation on your part. I would first play with the idea of "touching" the objects that were fetched. Perhaps updating a 'lastAccessed' date or something so that these objects will come across the merge as "updated". I suspect that is the easiest path.

Baring that, another option would be to broadcast a notification of those objects back to the main thread (using their NSManagedObjectID as the carrier across the thread boundaries) so that you can manually update them; however that is less than ideal.

Marcus S. Zarra
I clarified the question a bit and added in some code.The results controller delegate just implements `controllerDidChangeContent:` to reload the table after the results are merged into the main MOC.
Justin Williams
A: 

Hey Justin, could your fetchLimit be causing this?

The NSFetchedRequestController when initialized queries for the latest 100 results from the database (using setFetchLimit:). Even if there are 1000 rows, I only want 100 accessible at first.

You will still have a limit of 100 results on the NSFetchedResultsController no matter how many managed objects you insert & merge.

Do you have a sorting turned on? If so, some of the inserted managed objects might be showing up because they displace some of the existing 100 due to the ordering of results.

I've gone through the same issues with fetching "pages" of results, and went with a lower-bound constraint on my fetch (using the sorted attribute in an NSComparisonPredicate) and adjusting that with the last item in the most recent "page" of results.

I also tried the fetchLimit approach, and that worked too. You don't need to remove & rebuild the NSFetchedResultsController, you can just adjust the fetchLimit of the fetchRequest:

tableMaxItems += 100;
[myFRC.fetchRequest setFetchLimit:tableMaxItems];
ohhorob
A: 

Here is how I ended up fixing this. Prior to merging in the changes from my NSOperation's MOC, I iterate through the values stored in NSUpdatedObjectsKey and touch them with willAccessValueForKey. To get them into NSUpdatedObjects key I took Marcus' advice above and added a lastAccessed property that I set to the current date in the NSOperation if the object already existed in the persistent store..

- (void)mergeChanges:(NSNotification *)notification
{
  NSAssert([NSThread mainThread], @"Not on the main thread");  
  NSSet *updatedObjects = [[notification userInfo] objectForKey:NSUpdatedObjectsKey];

  for (NSManagedObject *object in updatedObjects)
  {
    [[managedObjectContext objectWithID:[object objectID]] willAccessValueForKey:nil];
  }

  [managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
Justin Williams
If an answer is correct it is customary to mark it as the answer :)
Marcus S. Zarra