IOS - MagicalRecord / AFNetworking / NSFetchedResultsController - background resynchronization process causes perpetual freeze

So the goal I'm trying to achieve

is a synchronization process that needs to be completed in the background using AFNetworking and Magical Record, but causes an eternal hang when the view controller connected to the NSFetchedResultsController is currently open or was open (but popped up).

The application synchronizes the first time the user opens the phone, and then always uses the data in the Core Data repository through the Magical Record structure. Then, when the user wants to make sure that the data is the latest version, they enter the settings and click "Re-Sync", which leads to the execution of the following code:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{ [[CoreDataOperator sharedOperator] commenceSync:self]; }); 

This will start the synchronization process using a singleton CoreDataOperator (subclass of NSObject - maybe it should be NSOperation?), Which runs the following code:

 [[ApiClient sharedClient] getDataRequest]; 

which then fires from this bad boy in a subclass of AFHTTPClient singleton:

 [[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 to publish this information and, when it succeeds, pass the information to the provided selector (I understand that this is general, but the request is not a problem)

NOW, AFNetworking is encoded in such a way that all the main selectors (in this case specifically, success and failure) are called in the main thread; therefore, to prevent the main thread from being blocked, the code that processes the response and prepares it for saving is sent back to the background thread:

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ id responseData; [self clearAndSaveGetData:responseData]; }); 

This then calls a function that calls persistence using the Magical Record framework (still in the background thread):

 NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread]; [DATAENTITY truncateAllInContext:localContext]; <!--- PROCESS DATA INTO DATAENTITY --> [localContext saveNestedContexts]; 

I chose saveNestedContexts because, since I am running in the background, I wanted it to go all the way to the default contexts, which I assume is the parent context? (but this has not been a problem yet).

Now this data can become thousands and thousands of rows, so I use NSFetchedResultsController to safely and efficiently access this data, and they are used in a different view controller than in the settings or on the main page.

Here are three cases:

  • The ViewController containing the FRC was not accessible (invisible and was not visible before) - background synchronization works flawlessly, minus a slight delay due to the stack being saved.
  • The ViewController containing the FRC was available and is currently visible - the HANGS background synchronization process due to the fact that it seems to be the recipient of contextual FRC updates.
  • The ViewController containing the FRC was previously available, but it is not currently visible (VC was knocked out with the following code: [self.navigationController popViewControllerAnimated:YES]; ) And FRC is set to nil in ViewDidUnload - the HANGS background synchronization process due of what appears to be FRC getting context updates (as in case 2).

[PS: after hanging for several minutes, I kill the debugger and it gives me SIGKILL to the following code, so I am sure that FRC receives context updates that cause it to hang:

 - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView; [tableView endUpdates]; <---- SIGKILL } 

Other important information:

  • I use 2 separate FRCs, one for the usual ALL data and one for searching
  • I use the cache for both FRC and a separate search FRC, which is cleared at the corresponding time points (before this contextual update).
  • FRCs are retrieved in mainthread (which causes a little freeze when there are THOUSAND data rows), and I was looking for a sample in the background, however this is not currently implemented.

QUESTION (s):

  • Why does this freeze occur where the VC is visible or popped up?
  • How can I make sure that FRC does not listen to the save, but uses what it has before the save is completed, and then updates the data (if this does not happen already and causes a hang)?
  • Will implementing a background selection (since lagging when opening VC using FRC to access thousands of data rows creates a noticeable lag, even with a cache and reduced predicate / section headers from 1 to 4 seconds) be viable? Too complicated? How can I do that?

Thank you, I hope that I was quite detailed in my question.

+6
source share
1 answer

I know this question is old, but since this is the usual magic with MagicalRecord, maybe the following will help someone.

The problem is caused by fetchRequest FRC, and the save operation blocks each other. For me this has been resolved:

Update to the latest version of MagicalRecord (2.1 at the time of writing).

Then do all your background save 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. }]; 
+1
source

Source: https://habr.com/ru/post/921332/


All Articles