2

I am trying to use AFIncrementalStore together with MagicalRecord.

I have a class Beer with few properties (from BreweryDB web service). To fetch beers I am using [Beer MR_fetchAllWithDelegate:self] which returns NSFetchedResultsController instance, (which I grab and assign to beerFetchController) and performs an actual fetch using that controller. As you can also see I set my view controller as a delegate of the the method, which in turns, sets that view controller as a delegate of the beerFetchController. So far so good. I have a table view that displays the beers, and I implement the method

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    [self.beersTable reloadData];
}

So whenever the AFIncrementalStore gets the data, puts it into the context, the controller is notified and in turn this method should be called, and the table will be refreshed. The problem is, this delegate method is not called at all! After some investigation i was able to draw some conclusions.

+ (NSFetchedResultsController *) MR_fetchAllWithDelegate:(id<NSFetchedResultsControllerDelegate>)delegate;
{
    return [self MR_fetchAllWithDelegate:delegate inContext:[NSManagedObjectContext MR_contextForCurrentThread]];
}


+ (NSFetchedResultsController *) MR_fetchAllWithDelegate:(id<NSFetchedResultsControllerDelegate>)delegate inContext:(NSManagedObjectContext *)context;
{
    NSFetchRequest *request = [self MR_requestAllInContext:context];
    NSFetchedResultsController *controller = [self MR_fetchController:request delegate:delegate useFileCache:NO groupedBy:nil inContext:context];

    [self MR_performFetch:controller];
    return controller;
}

The issue is that, [NSManagedObjectContext MR_contextForCurrentThread], will return a child context who's parent is the backing context that AFIncrementalStore uses internally. AFIncrementalStore saves to the parent, not the child, therefore the controller has no idea that anything changed, and the delegate method is not called. I was able to modify the method like this :

+ (NSFetchedResultsController *) MR_fetchAllWithDelegate:(id<NSFetchedResultsControllerDelegate>)delegate inContext:(NSManagedObjectContext *)context;
{
    NSFetchRequest *request = [self MR_requestAllInContext:context];
    NSFetchedResultsController *controller = [self MR_fetchController:request delegate:delegate useFileCache:NO groupedBy:nil inContext:context.parentContext];

    [self MR_performFetch:controller];
    return controller;
}

Instead of binding the controller to context returned by MR_contextFromCurrentThread, I bind it to its parent. And then, as expected stuff works as it should, context is changed, controller sees that, and the delegate method is called. The question is, is there any way to, push the changes from the parent context to the child context so I do not have to change the method implementation ?

+ (NSManagedObjectContext *) MR_contextForCurrentThread;
{
    if ([NSThread isMainThread])
    {
        return [self MR_defaultContext];
    }
    else
    {
        NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary];
        NSManagedObjectContext *threadContext = [threadDict objectForKey:kMagicalRecordManagedObjectContextKey];
        if (threadContext == nil)
        {
            threadContext = [self MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]];
            [threadDict setObject:threadContext forKey:kMagicalRecordManagedObjectContextKey];
        }
        return threadContext;
    }
}
foFox
  • 1,124
  • 1
  • 14
  • 24
  • What thread are you calling `MR_fetchAllWithDelegate` on? If it's the main thread it ought to use the defaultContext and you should be ok then. – Mike Pollard May 21 '13 at 15:17
  • It is a main thread, I call it from the viewDidLoad on the controller. I just don't want to modify the internals of Magical Record, I don't know what the side effects might be, especially that I probably will forget that I modified it in a certain way soon. If I leave it like this, in a modified way, I can only use that method from a main thread. – foFox May 21 '13 at 15:30
  • What have you setup the MR_defaultContext to be? – Mike Pollard May 21 '13 at 15:44
  • It sounds like you need to setup a merge operation from the parent context to the child context using `MR_observeContext`. I'd like to know how the MR_defaultContext is configured initially since `MR_contextForCurrentThread` returns `MR_defaultContext` when run on the main thread. – Mike Pollard May 21 '13 at 15:56
  • added that contextForCurrentThread. – foFox May 21 '13 at 16:09
  • Yep, so `[self MR_defaultContext]` is your context. How did that become a child of the AFIncrementalStore backingContext? – Mike Pollard May 21 '13 at 16:14
  • Generally, if you did this: `childContext = [self MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]]`, then to get changes that are made to MR_defaultContext to propagate up to childContext you need to do this: `[childContext MR_observeContext:[NSManagedObjectContext MR_defaultContext]];` – Mike Pollard May 21 '13 at 16:21

0 Answers0