Search code examples
iosobjective-cnsoperationnsoperationqueue

Chaining `NSOperation` : Pass result from an operation to the next one


I've been looking for a way to pass results for chained NSOperation. For example, lets assume we have 3 operations chained:

  1. Operation1 to download JSON data from server
  2. Operation2 to parse & model JSON received
  3. Operation3 to download user images

So Op3 would be dependent on Op2, which is dependent on Op1. But I'm looking for way to pass results from Op1 -> Op2, then from Op2 -> Op3 as:

[operation1 startWithURL:url];
[operation2 parseJSONfromOp1IntoModel:JSONData];
[operation3 downloadUserImagesForUser: UserModelObject];

and nesting blocks doesn't seem to be a clean readable solution, any idea?


Solution

  • If you want to chain operations, but don't like the nesting, you can use NSOperation subclasses, and then define your own completion handlers:

    DownloadOperation *downloadOperation = [[DownloadOperation alloc] initWithURL:url];
    ParseOperation *parseOperation = [[ParseOperation alloc] init];
    DownloadImagesOperation *downloadImagesOperation = [[DownloadImagesOperation alloc] init];
    
    downloadOperation.downloadCompletionHandler = ^(NSData *data, NSError *error) {
        if (error != nil) {
            NSLog(@"%@", error);
            return;
        }
    
        parseOperation.data = data;
        [queue addOperation:parseOperation];
    };
    
    parseOperation.parseCompletionHandler = ^(NSDictionary *dictionary, NSError *error) {
        if (error != nil) {
            NSLog(@"%@", error);
            return;
        }
    
        NSArray *images = ...;
    
        downloadImagesOperation.images = images;
        [queue addOperation:downloadImagesOperation];
    };
    
    [queue addOperation:downloadOperation];
    

    Frankly, though, I'm not sure that's any more intuitive than the nested approach:

    DownloadOperation *downloadOperation = [[DownloadOperation alloc] initWithURL:url downloadCompletionHandler:^(NSData *data, NSError *error) {
        if (error != nil) {
            NSLog(@"%@", error);
            return;
        }
    
        ParseOperation *parseOperation = [[ParseOperation alloc] initWithURL:data parseCompletionHandler:^(NSDictionary *dictionary, NSError *error) {
            if (error != nil) {
                NSLog(@"%@", error);
                return;
            }
    
            NSArray *images = ...
    
            DownloadImagesOperation *downloadImagesOperation = [[DownloadImagesOperation alloc] initWithImages:images imageDownloadCompletionHandler:^(NSError *error) {
                if (error != nil) {
                    NSLog(@"%@", error);
                    return;
                }
    
                // everything OK
            }];
            [queue addOperation:downloadImagesOperation];
        }];
        [queue addOperation:parseOperation];
    }];
    [queue addOperation:downloadOperation];
    

    By the way, the above assumes that you're familiar with subclassing NSOperation, especially the subtleties of creating an asynchronous NSOperation subclass (and doing all of the necessary KVO). If you need examples of how that's done, let me know.