4

I am downloading data from different links using ASIHTTPRequest and NSOperationQueue to

download in background thread. When a request has finished i parse in using requestFinished

delegate method of ASIHTTPRequest. I want to update the data in tableview when all requests in

the queue has completed. Is there any way to know when an NSOperationQueue has processed all

requests? i mean queue has any variable like 'isEmpty' or any delegate method like 'queueDidCompletedAllOperation'?

please help.

Here is the code:

//source

@interface SourceModel : NSObject

@property (nonatomic, retain) NSString * link;

@property (nonatomic, retain) NSString * name;

@end


//for rssGroup

@interface CompleteRSSDataModel : NSObject

@property (nonatomic,strong) SourceModel * source;

@property (nonatomic,strong) KissXMLParser * parser;

@property (nonatomic,strong) NSArray * rssArticles;

@end

- (void)viewDidLoad
{
       for (int index=0; index<[rssGroups count]; index++) {

            NSString * urlString = [[[rssGroups objectAtIndex:index] source] link];

            NSURL *url = [NSURL URLWithString:urlString];

            ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; [request setDelegate:self];

            //set this request's tag to group index of this source(link). See requestFinished for use of this :)
            [request setTag:index];

            [self.queue addOperation:request];
        }

}


- (void)requestFinished:(ASIHTTPRequest *)request {

    NSLog(@"%@",@"RSS Data got from internet successfully :))");


    int groupIndex = [request tag];

    CompleteRSSDataModel * group = [rssGroups objectAtIndex:groupIndex];

    group.parser = [[KissXMLParser alloc]initWithData:[request responseData]];

    if (group.parser == nil) {

        NSLog(@"%@",@"Failed - Error in parsing data :((");
    }

    else {
        NSLog(@"%@",@"Data Parsed successfully :))");

        group.rssArticles = [group.parser itemsInRss];

        //So i want to check here that queue is empty, reload data, but as my information, i don't know any method like hasCompletedAllRequested 

        //if(self.queue hasCompletedAllRequests) {

        //     [self.tableview reloadData];
        //}


    }
}

- (void)requestFailed:(ASIHTTPRequest *)request {

    NSLog(@"%@",@"Error in Getting RSS Data from internet:((");

}
iMemon
  • 1,095
  • 1
  • 12
  • 21
  • You'll probably want to share what you've already tried here -- i.e., research, problems encountered, etc. A wall of code doesn't really do anything for anyone who would like to help. –  Aug 11 '12 at 04:57
  • May be you are right. I need to edit the question. Thanks for suggestion – iMemon Aug 11 '12 at 05:12

3 Answers3

10

If all operation has been completed then the operations array count will be zero.

To check this you can use Key Value Observation Coding to observer the operations key of NSOperationQueue

To set the observer for the key opertions will be like below:

[self.queue addObserver:self forKeyPath:@"operations" options:0 context:NULL];

Then do this in your observeValueForKeyPath like below:

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object 
                         change:(NSDictionary *)change context:(void *)context
{
    if (object == self.queue && [keyPath isEqualToString:@"operations"]) {
        if ([self.queue.operations count] == 0) {
            // Do something here when all operations has completed
            NSLog(@"queue has completed");
        }
    }
    else {
        [super observeValueForKeyPath:keyPath ofObject:object 
                               change:change context:context];
    }
}

After iOS 4.0 you can use the property operationCount like self.queue.operationCount == 0 instead of checking like this [self.queue.operations count] == 0

Shanmugaraja G
  • 2,778
  • 4
  • 31
  • 47
2

I know this has already been answered but for future readers, if you are using AFNetworking and more specifically, AFHTTPRequestOperation you can do something like this:

NSString *urlPath = [NSString stringWithFormat:@"%@%@", baseUrl, file];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlPath]];

AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];

[operation setCompletionBlockWithSuccess:
 ^(AFHTTPRequestOperation *operation, id responseObject) {
   if ([[queue operations] count] ==0) {
       NSNotification * success = [NSNotification notificationWithName:@"TotalDone" object:[NSNumber numberWithBool: YES]];
      [[NSNotificationCenter defaultCenter] postNotification:success];
      queueSize = 0;
   } else {

       //get total queue size by the first success and add 1 back
       if (queueSize ==0) {
           queueSize = [[queue operations] count] +1.0;
       }
       float progress = (float)(queueSize-[[queue operations] count])/queueSize;
       NSNumber * totProgress = [NSNumber numberWithFloat:progress];
       NSNotification * totalProgressNotification = [NSNotification notificationWithName:@"TotalProgress"
                                                                                           object:totProgress];
       [[NSNotificationCenter defaultCenter] postNotification:totalProgressNotification];
   }


} failure:^(AFHTTPRequestOperation *operation, NSError *error) 
        NSLog(@"Error: %@", error);
        }];

This is for a downloader that adds a download to a NSOperationQueue, then notifies two progress bars: 1 for file progress, and 1 for total progress.

Hope this helps

onetwopunch
  • 3,279
  • 2
  • 29
  • 44
0

A couple of options leap out at me:

  1. Use ASINetworkQueue instead, and set queueDidFinishSelector, so it will tell you when it's done. This also gives you the opportunity to not only update UIProgressView views for not only the individual rows, but also another one for the entire download process.

  2. Could you add something to your NSOperationQueue that would simply invoke a method to then update your UI (in the main queue, of course)? By adding it to the queue, it wouldn't get to it until it cleared the queue. Or in another queue, invoke waitUntilFinished, at which point you can update your UI.

  3. Looking at your comments, it looks like you're updating your UI in requestFinished, but are dissatisfied because you think it might be better to wait for all of the updates to take place. Personally, I much prefer to update the UI, request by request, that way if it's slow, you get interim feedback, rather than waiting for everything. This has to be done gracefully, but I like updating the UI as I go along. Admittedly, some processes don't lend themselves to that.

On this last point, I think the trick is to do these updates so it's not distracting. Specifically, if your UI is a UITableView, you probably do not want to do a tableView.reloadData, which will reload the entire table. Instead, you probably want to check to see if the cell is still loaded (via cellForRowAtIndexPath, the UITableView method, not to be confused with the UITableViewController method tableView:cellForRowAtIndexPath) and if it is, update that one row, e.g.:

NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
if (cell)
{
    // you can either update the controls in the cell directly, or alternatively, just
    // call reloadRowsAtIndexPaths to have the tableView use your data source:

    [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                          withRowAnimation:UITableViewRowAnimationFade];
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • You are right. currently i am updating ui in requestFinished, but i felt that was not the right way. I think ASINetworkQueue may be a good option. I am going to try it. But i want to ask will it do all downloading in background like NSOperationQueue? – iMemon Aug 11 '12 at 05:34
  • Yes, it has in my code. Also, it provides some other perks (e.g. if you use UIProgressView, you can have one for the entire queued jobs), too. – Rob Aug 11 '12 at 05:36
  • currently i am updating ui in requestFinished as u stated. But problem was that when i have multiple requests, i need to update the ui after all requests have been completed. Then i solved it using [self.queue.operation count] message. When it becomes zero, i could safely update ui. Now i can show the download progress using this method. Thanks anyway :) – iMemon Aug 13 '12 at 07:34
  • @iMemon I personally think it's good to update the UI after each and every `requestFinished`. The trick is to not update the entire UI, though, but just the appropriate row. I've updated my answer accordingly. Clearly, you can wait until the end (in which case I wouldn't recommend `self.queue.operation.count`, but rather I'd use `setQueueDidFinishSelector` to just specify what method you want to have called when the queue is completed), but I think it's a much better user experience to seamlessly update the UI as the data comes in, for each `requestFinished`. – Rob Aug 13 '12 at 13:33