1
votes

I'm trying to update NSManagedObject's in the background based on properties from a network fetch. Having trouble wrapping my head around concurrency. What I have tried is

  1. fetch NSManagedObjects(Asset) on a localcontext (NSPrivateQueueType)
  2. enumerate over assets array perform a network GET request for each asset and add it to a dispatch_group
  3. Within the completion block from the network call, map updated values to the asset (This is my problem)

My code looks something like this

__block dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    __block dispatch_group_t taskGroup = dispatch_group_create();

    [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
        NSArray *assets = [Asset MR_findAllInContext:localContext];
        for (Asset *asset in assets) {
            dispatch_group_enter(taskGroup);
            dispatch_async(queue, ^{
                [stockClient GET:@"/v1/public/yql" parameters:asset.assetSymbol success:^(NSURLSessionDataTask *task, id responseObject) {
                   //TODO:Check response
                    NSDictionary *stockData = [[[responseObject objectForKey:@"query"] objectForKey:@"results"] objectForKey:@"quote"];
                    [asset mapPropertiesFrom:stockData];//----------> How do I access the localcontext queue?
                    dispatch_group_leave(taskGroup);

                } failure:^(NSURLSessionDataTask *task, NSError *error) {
                    //TODO:Error handling here
                    dispatch_group_leave(taskGroup);
                }];
            });
        }

        dispatch_group_notify(taskGroup, dispatch_get_main_queue(), ^{
            NSLog(@"All Tasks are completed");
        });
    }];
}

When I try to update the asset(NSManagedObject) from the network completion block

[asset mapPropertiesFrom:stockData];

I receive

CoreData could not fulfill a fault for

I suspect this is because the completion block is on the main queue and my asset was fetched on a private queue and you can't access NSManagedObjects from different queues...but I'm not sure is this is the problem and if it is how to resolve it.

So,how could I complete such a task? Am I going about this completely wrong?

EDIT

Made some changes based on comments below, but I'm receiving error

//Setup Dispatch Group
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t taskGroup = dispatch_group_create();

    NSArray *assets = [Asset MR_findAll];
    for (Asset *asset in assets) {
        dispatch_group_enter(taskGroup);
        dispatch_async(queue, ^{
            NSManagedObjectContext *localContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
            localContext.parentContext = [NSManagedObjectContext MR_defaultContext];
            Asset *localAsset = [asset MR_inContext:localContext];
            [stockClient updateStockDataFor:localAsset completionHandler:^(NSDictionary *stockData, NSError *error) {
                NSManagedObjectContext *responseContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
                responseContext.parentContext = [NSManagedObjectContext MR_defaultContext];
                Asset *responseAsset = [localAsset MR_inContext:responseContext];
                [responseAsset mapPropertiesFrom:stockData];
                [responseContext MR_saveToPersistentStoreAndWait];
                 dispatch_group_leave(taskGroup);

            }];
        });
    }
    dispatch_group_notify(taskGroup, dispatch_get_main_queue(), ^{
        NSLog(@"Tasks are Completed");
    });
}

Error:

Can only use -performBlock: on an NSManagedObjectContext that was created with a queue.

2

2 Answers

1
votes

As far as MagicalRecord is concerned here, you are dispatching a background task in the background. Basically, your queues are out of sync. I suggest you use a single background queue to save your data.

Try something like this:

__block dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__block dispatch_group_t taskGroup = dispatch_group_create();


    NSArray *assets = [Asset MR_findAll];
    for (Asset *asset in assets) {
        dispatch_group_enter(taskGroup);
        dispatch_async(queue, ^{
            NSManagedObjectContext = //create a confinement context
            Asset *localAsset = [asset MR_inContext:localContext];
            [stockClient GET:@"/v1/public/yql" parameters:asset.assetSymbol success:^(NSURLSessionDataTask *task, id responseObject) {
               //TODO:Check response
                NSManagedObjectContext *responseContext = //create a confinement context
                Asset *responseAsset = [localAsset MR_inContext:responseContext];
                NSDictionary *stockData = [[[responseObject objectForKey:@"query"] objectForKey:@"results"] objectForKey:@"quote"];
                [asset mapPropertiesFrom:stockData];//----------> How do I access the localcontext queue?
                [responseContext MR_saveToPersistentStoreAndWait];
                dispatch_group_leave(taskGroup);

            } failure:^(NSURLSessionDataTask *task, NSError *error) {
                //TODO:Error handling here
                dispatch_group_leave(taskGroup);
            }];
        });
    }

    dispatch_group_notify(taskGroup, dispatch_get_main_queue(), ^{
        NSLog(@"All Tasks are completed");
    });

}

2
votes

Wrap the calls that will read/write anything from core data in a perform block, using:

- (void)performBlock:(void (^)())block

From the docs:

You use this method to send messages to managed objects if the context was initialized using NSPrivateQueueConcurrencyType or NSMainQueueConcurrencyType.

This method encapsulates an autorelease pool and a call to processPendingChanges.

As long as you use this method for your calls there shouldn't be any problems.

I don't understand the second part of your question but in my app im performing updates/reads from network calls all the time using this method. Alternatively you can just dispatch to the main queue just the core data parts. That is, do the network thing on the background and when you need core data use a dispatch queue to the main queue.