is a syncing process that is supposed to be completed in the background using AFNetworking and Magical Record, but causes a perpetual hang when a view controller hooked up to a NSFetchedResultsController is currently open or has been opened (but popped).
The app syncs the first time the user opens the phone and then uses the data always in the Core Data persistance store via the Magical Record framework. Then when the user wants to make sure that there data is the most recent version, they go into settings and click "Re-Sync", which causes the following code to execute:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
[[CoreDataOperator sharedOperator] commenceSync:self];
});
This starts the syncing process using the singleton CoreDataOperator (subclass of NSObject -- maybe it should be NSOperation?), which fires off the following code:
[[ApiClient sharedClient] getDataRequest];
which then then fires off this bad boy in the singleton AFHTTPClient subclass:
[[ApiClient sharedClient] postPath:url parameters:dict
success:^(AFHTTPRequestOperation *operation, id responseObject) {
[request.sender performSelector:request.succeeded withObject:response];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[request.sender performSelector:request.failed withObject:response];
}
];
which effectively says: AFHTTPClient post this infomation and when it succeeds, pass back the information to the provided selector (I understand that this is generic, but the request isn't the issue)
NOW, AFNetworking is coded such that all completion selectors (in this case specifically, success and failure) are called on the main thread; so in order to prevent blocking the main thread, the code that processes the response and prepares it for saving is sent back into the background thread:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
id responseData;
[self clearAndSaveGetData:responseData];
});
This then calls the function that causes the saving using the Magical Record framework (still in background thread):
NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];
[DATAENTITY truncateAllInContext:localContext];
<!--- PROCESS DATA INTO DATAENTITY -->
[localContext saveNestedContexts];
I chose saveNestedContexts
because since I am working in the background, I wanted it pushed all the way up to the defaults contexts, which I'm assuming is a parent context? (but this so far hasn't been an issue).
Now, this data can become thousands and thousands of rows, so I'm using a NSFetchedResultsController to access this data safely and efficiently, and they are used in a different view controller than the settings or main page.
[self.navigationController popViewControllerAnimated:YES];
) AND the FRC is set to nil in ViewDidUnload - background sync process HANGS due to what appears to be the FRC receiving context updates (just like in case 2).[PS: after hanging for a few minutes, I kill the debugger and it gives my a SIGKILL on the following code, which is why I'm assuing the FRC is receiving context updates that causes it to hang:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
[tableView endUpdates]; <---- SIGKILL
}
Thank you, hope I was detailed enough in my question.
I know this question is old but since this is a common gotcha with MagicalRecord maybe the following will help somebody.
The problem is caused by the fetchRequest
of the FRC and the save operation deadlocking each other. The following solved it for me:
Update to the latest MagicalRecord release (2.1 at the time of writing).
Then do all your background saves using the following:
MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
// create new objects, update existing objects. make sure you're only
// accessing objects inside localContext
[myObjectFromOutsideTheBlock MR_inContext:localContext]; //is your friend
}
completion:^(BOOL success, NSError *error) {
// this gets called in the main queue. safe to update UI.
}];
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With