NSOperationQueue support and background support

I have some problems developing a useful background support strategy for the NSOperationQueue class. In particular, I have an NSOperation group that does the following:

  • Download file from the Internet
  • File parsing
  • Import Data File into Core Data li>

Operations are inserted in a sequential queue. As soon as the operation is completed, the next one will begin.

I need to stop (or continue) operations when the application enters the background. From these discussions ( Does AFNetworking support support? Both the NSOperations Queue and the application exit for processing ) I see the best way is to cancel operations and use the isCancelled property in each operation. Then, checking the key point of the operation against this property, it allows you to roll back the execution state (of the current operation) when the application enters the background.

Based on an Apple template that highlights background support, how can I manage this situation? Can I just cancel the operation or wait for the current operation to complete? See comments for more details.

 - (void)applicationDidEnterBackground:(UIApplication *)application { bgTask = [application beginBackgroundTaskWithExpirationHandler:^{ // Do I have to call -cancelAllOperations or // -waitUntilAllOperationsAreFinished or both? [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }]; // Start the long-running task and return immediately. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // What about here? [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }); } 

Thanks in advance.

Edit

If the NSOperation main method executes the code below, how can I follow the cancellation response event pattern?

 - (void)main { // 1- download // 2- parse // 2.1 read file location // 2.2 load into memory // 3- import // 3.1 fetch core data request // 3.2 if data is not present, insert it (or update) // 3.3 save data into persistent store coordinator } 

Each method described by me contains various stages (non-atomic operations, except for the loaded one). Thus, cancellation can occur at each of these steps (not in a predetermined way). Can I check the isCancelled property before each step? It works?

Edit 2 based on Tammo Freese editing

I understand what you mean with your editing code. But I'm worried about the following: A cancellation request (the user can click the home button) can occur at any point in the main run, so if I just go back, the operation status will be distorted. Do I need to clear my condition before returning? What do you think?

The problem that I described can happen when I use synchronization operations (operations that are executed synchronously in the same thread that they start). For example, if main loads a file (loading is done via +sendSynchronousRequest:returningResponse:error ) and the application is placed in the background image, what could happen? How to manage this situation?

 // download if ([self isCancelled]) return; // downloading here <-- here the app is put in background 

Obviously, I think that when the application is then placed in the foreground, the operation starts again since it was canceled. In other words, he is forced to not maintain his condition. I'm wrong?

+4
source share
1 answer

If you understood correctly, you have an NSOperationQueue , and if your application goes into the background, you would like

  • cancel all operations and
  • wait for the cancellation to complete.

Usually this should not take too much time, so this should be enough:

 - (void)applicationDidEnterBackground:(UIApplication *)application { [_queue cancelAllOperations]; [_queue waitUntilAllOperationsAreFinished]; } 

The definition of โ€œtoo much timeโ€ here is about five seconds: if you block -applicationDidEnterBackground: for longer, your application will be terminated and cleared from memory.

Assume that the completion of canceled operations takes more than 5 seconds. Then you need to do the wait in the background (see Comments for explanation):

 - (void)applicationDidEnterBackground:(UIApplication *)application { bgTask = [application beginBackgroundTaskWithExpirationHandler:^{ // If this block is called, our background time of normally 10 minutes // is almost exceeded. That would mean one of the cancelled operations // is not finished even 10 minutes after cancellation (!). // This should not happen. // What we do anyway is tell iOS that our background task has ended, // as otherwise our app will be killed instead of suspended. [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }]; // Normally this one is fast, so we do it outside the asynchronous block. [_queue cancelAllOperations]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // Wait until all the cancelled operations are finished. [_queue waitUntilAllOperationsAreFinished]; // Dispatch to the main queue if bgTask is not atomic dispatch_async(dispatch_get_main_queue(), ^{ [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }); }); } 

So, basically we tell iOS that we need some time to complete the task, and when the task ends, time ends, we tell iOS about the completion of our task.

Edit

To answer the question in your edit: To answer the cancellation, just mark the cancellation, please, and return from the -main method. A canceled operation does not immediately end, but ends when -main returns.

 - (void)main { // 1- download if ([self isCancelled]) return; // 2- parse // 2.1 read file location // 2.2 load into memory while (![self isCancelled] && [self hasNextLineToParse]) { // ... } // 3- import // 3.1 fetch core data request if ([self isCancelled]) return; // 3.2 if data is not present, insert it (or update) // 3.3 save data into persistent store coordinator } 

If you do not check the canceled flag at all in -main , the operation will not respond to cancellation, but will continue until it is completed.

Edit 2

If the operation is canceled, nothing happens to it, except that the isCancelled flag is true. The code above in my original answer waits in the background until the operation is completed (either responded to the cancellation, or just finished, assuming that it does not take 10 minutes to cancel).

Of course, when reacting to isCancelled in our operation, you must make sure that you leave the operation in a non-corrupt state, for example, immediately after loading (just ignoring the data) or after writing all the data.

You are right if the operation is canceled, but is still performed when switching to the foreground, this operation will complete the download, and then (if you programmed it this way) respond to the cancellation and basically discard the downloaded data,

Instead, you can not cancel the operations, but wait for their completion (provided that they take less than 10 minutes). To do this, simply delete the line [_queue cancelAllOperations]; .

+15
source

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


All Articles