ItsASecret
ItsASecret

Reputation: 2649

NSOperationQueue INSIDE an NSOperation

I created an NSOperation which goal is to download a few images (like 20) from 20 URLs. So inside this NSOperation I create 20 AFImageRequestOperation add them in an NSOperationQueue and call -waitUntilAllOperationsAreFinished on the queue.

Problem is, it doesn't wait, it returns instantly. Here is the code

- (void)main {
    NSArray *array = [I have the 20 links stored in this array];

    self.queue = [[NSOperationQueue alloc]init];
    self.queue.maxConcurrentOperationCount = 1;

    for (int i = 0; i < array.count; i++) {                
        NSURL *url = [NSURL URLWithString:[array objectAtIndex:i]];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];

        AFImageRequestOperation *op = [AFImageRequestOperation imageRequestOperationWithRequest:request imageProcessingBlock:^UIImage *(UIImage *image) {
            return image;
        } success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
        // SUCCESS BLOCK (not relevant)
        } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
        // FAILURE BLOCK (not relevant)
        }];

        [self.queue addOperation:op];
    }

    [self.queue waitUntilAllOperationsAreFinished]; // Here is the problem it doesn't wait
    DDLogWarn(@"-- %@ FINISHED --", self);

}

In the console, the DDLogWarn(@"-- %@ FINISHED --", self); prints before every operations even started, so my guess was that the waitUntilAllOperationsAreFinished didn't do its job and didn't wait, but the 20 Operations are still running after that which may means that the main didn't return yet, so I don't know what to think anymore.

EDIT 1 : Actually I'm wondering, if my DDLog inside the success and failure blocks because waitUntilAllOperationsAreFinished is blocking the thread until all operations complete. That may explain why I don't see anything in happening and everything suddenly.

Upvotes: 2

Views: 1623

Answers (2)

Rory O&#39;Bryan
Rory O&#39;Bryan

Reputation: 1904

I have found it useful to create an NSOperation that contains other NSOperations for similar reasons to you, i.e. I have a lot of smaller tasks that make up a bigger task and I would like to treat the bigger task as a single unit and be informed when it has completed. I also need to serialise the running of the bigger tasks, so only one runs at a time, but when each big task runs it can perform multiple concurrent operations within itself.

It seemed to me, like you, that creating an NSOperation to manage the big task was a good way to go, plus I hadn't read anything in the documentation that says not to do this.

It looks like your code may be working after all so you could continue to use an NSOperation.

Depending on your circumstances blocking the thread may be reasonable. If blocking isn't reasonable but you wanted to continue using an NSOperation you would need to create a "Concurrent" NSOperation see Concurrency Programming Guide: Configuring Operations for Concurrent Execution

If you only allow one image download at a time, you could use @jackslashs suggestion to signal the end of the operation, or if you want to allow concurrent image downloads then you could use a single NSBlockOperation as the final operation and use -[NSOperation addDependency:] to make it dependant on all the other operations so it would run last.

When you get the signal that everything is finished and you can set the isFinished and isExecuting flags appropriately as described in the documentation to finalise your main NSOperation.

Admittedly this has some level of complexity, but you may find it useful because once that complexity is hidden inside an NSOperation the code outside may be simpler as was the case for me.

If you do decide to create a Concurrent NSOperation you may find the Apple sample code LinkedImageFetcher : QRunLoopOperation useful as a starting point.

Upvotes: 5

jackslash
jackslash

Reputation: 8570

This code doesn't need to be inside an NSOperation. Instead make a class, perhaps a singleton, that has an operation queue and make a method called

-(void)getImagesFromArray:(NSArray *)array

or something like that and your code above will work fine enqueueing onto that queue. You don't need to call waitUntilAllOperationsAreFinished. Thats a blocking call. If your queue has a max operation count of 1 you can just add another operation to it once you have added all the network operations and then when it executes you know all the others have finished. You could just add a simple block operation on to the end:

//add all the network operations in a loop
[self.operationQueue addOperation:[NSBlockOperation blockOperationWithBlock:^{
    //this is the last operation in the queue. Therefore all other network operations have finished
}]];

Upvotes: 1

Related Questions