0

I'm using the AFNetworking library, which is excellent, however I'm having trouble keeping track of operations in the NSOperationQueue. I am adding NSOperation objects to the NSOperationQueue, and I need to keep track of progress - so update a UIProgressView to show how far the queue is to completion and then also execute a block of code once the queue is complete.

I've tried KVO - using the answer here: Get notification when NSOperationQueue finishes all tasks however I come across the problem (elaborated on the second answer down there) where sometimes operations in the queue may complete fast enough to temporarily decrement the operationCount property to 0 - which then cause issues with the code in the accepted answer - i.e. prematurely execute the code to be executed after all objects in the queue have finished and progress tracking will not be accurate as a result.

A variation I've tried is checking for operationCount == 0 in the success block of each NSOperation that I add to the NSOperationQueue and then executing code based on that, e.g.

    [AFImageRequestOperation *imgRequest = [AFImageRequestOperation imageRequestOperationWithRequest:urlRequest success:^(UIImage *image) {

     //Process image & save

            if(operationQ.operationCount == 0){
              // execute completion of Queue code here
            }
            else {
              // track progress of the queue here and update UIProgressView
            }
    }];

However, I come up with the same issue as I do with KVO.

I've thought about using GCD with a dispatch queue using a completion block - so asynchronously dispatch an NSOperationQueue and then execute the completion block but that doesn't solve my issue with regard to keeping track of the queue progress to update UIProgressView.

Also not used

AFHttpClient enqueueBatchOfHTTPRequestOperations:(NSArray *) progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations)progressBlock completionBlock:^(NSArray *operations)completionBlock

since my images are coming from a few different URLs (rather than one base url).

Any suggestions or pointers will be appreciated. Thanks.

Just a final update:

Solved this issue using the AFHTTPClient enqueueBatchOfHTTPRequestOperations in the end with the help of Matt (see accepted answer) and note the comments as well.

I did come across another solution that does not make use of AFHTTPClient but just NSOperationQueue on its own. I've included this as well in case it's of any use to anyone, but if you're using the AFNetworking Library I'd recommend the accepted answer (since it's most elegant and easy to implement).

Community
  • 1
  • 1
SMSidat
  • 1,163
  • 1
  • 15
  • 34
  • Did you see the 2nd answer to the linked question? Thats one possible approach to solving the problem. – Cory Powers May 22 '13 at 15:49
  • I did take a look at that Cory, and I tried using dependancies but it still didn't give me a solution, unless my application of that approach was incorrect.. – SMSidat May 22 '13 at 21:07

2 Answers2

4

AFHTTPClient -enqueueBatchOfHTTPRequestOperations:progressBlock:completionBlock: is the correct way to do this. The method takes an array of request operations, which can be constructed from any arbitrary requests—not just ones sharing a domain.

mattt
  • 19,544
  • 7
  • 73
  • 84
  • Does it not matter whether the requests share the base url of the AFHTTPClient? Also what would be a suitable base url when initialising the AFHTTPClient, or can the init method be called without assigning it, i.e. [[AFHTTPClient alloc] init]? – SMSidat May 22 '13 at 21:03
  • I just tried it anyway and attached an arbitrary base url (e.g. http://www.google.com) - and it seems to be exhibiting the same behaviour as my code above. I think what's happening is that the completion block executes once the items are all released from the queue but even before the success block for each queue item has completed execution - is there a way to delay execution of the completion block in AFHTTPClient until all code in the operation including the success block attached to each, is complete? – SMSidat May 22 '13 at 21:27
  • 2
    There's no guarantee about the timing of execution for completion blocks. My advice is to not use completionBlocks on operations in a batch, and do all of the necessary work once in the batch completion block. – mattt May 23 '13 at 15:21
  • Thank you Matt, it does seem to be the most elegant solution, and just to add to your comment - what actually solved my problem in the end was using the outputstream of the operation (as detailed here: http://stackoverflow.com/questions/8372661/how-to-download-a-file-and-save-it-to-the-documents-directory-with-afnetworking/8373637#8373637) to save the file - previously I was doing this via the operation completion block manually (converting image to data and then writeToFile) and so there was no guarantee it was written in time for the batch completion block. – SMSidat May 23 '13 at 16:05
  • Just one more thing to add - I see that the batch completion block provides access to the array of operations in the batch along with the response data (e.g. the response UIImage), which would allow one to do all the processing in this batch completion block rather than in the success block of each operation, ensuring everything is timely, as you suggested. – SMSidat May 23 '13 at 16:10
2

Another (not as elegant) solution, if you're only using NSOperationQueue and not the AFHTTPClient, is the following (assuming the following code will be in some loop to create multiple requests and add to the NSOperationQueue).

       [AFImageRequestOperation *imgRequest = [AFImageRequestOperation imageRequestOperationWithRequest:urlRequest success:^(UIImage *image) {

     //Process image & save

      operationNum++ 
      //initially operationNum set to zero, so this will now increment to 1 on first run of the loop

      if(operationNum == totalNumOperations){ 
         //totalNumOperations would be set to the total number of operations you intend to add to the queue (pre-determined e.g. by [array count] property which would also be how many times the loop will run)

         // code to execute when queue is finished here
       }
       else {
          // track progress of the queue here and update UIProgressView

          float progress = (float)operationNum / totalNumOperations
          [progView setProgress:progress] //set the UIProgressView.progress property
        }
     }];

Adding these NSOperation objects to the NSOperationQueue will ensure the success block of each operation will complete before executing the queue completion code which is embedded in the success block of each NSOperation object. Note NSOperationQueue.operationCount property isn't used since it is not reliable on fast operations since there may be an state in between an operation exiting a queue and just before the next one is added where the operationCount is zero and so if we compared NSOperationQueue.operationCount = 0 instead then the completion code for the queue would execute prematurely.

SMSidat
  • 1,163
  • 1
  • 15
  • 34