Chaining `NSOperation` : 将一个操作的结果传递给下一个操作

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

我一直在寻找一种方法来传递链式 NSOperation 的结果。例如,假设我们有 3 个链接的操作:

  1. Operation1 从服务器下载 JSON 数据
  2. Operation2 解析和建模 JSON 收到
  3. Operation3下载用户图片

所以 Op3 将依赖于 Op2,而 Op2 又依赖于 Op1。但我正在寻找从 Op1 -> Op2,然后从 Op2 -> Op3 传递结果的方法:

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

嵌套块似乎不是一个清晰可读的解决方案,知道吗?

正在创建链式操作:

从 Op1 的完成块中创建 Op2,然后使用委托或类似的东西来设置对新创建的操作的依赖。您可以使用此模式链接任意多个。要在完成块中传递结果,您不能使用 NSOperation 上的 completionBlock。您将需要定义自己的(就像我对 almostFinished 所做的那样)以便传递结果。

- (void)someMethod {
    Operation1 *operation1 = [[Operation1 alloc] init];
    operation1.almostFinished = ^(id op1Result) {

        Operation2 *operation2 = [[Operation2 alloc] initWithResultFromOp1: op1Result];
        operation2.almostFinished = ^(id op2Result) {

            Operation3 *operation3 = [[Operation3 alloc] initWithResultFromOp2:op2Result];
            operation3.completionBlock = ^{
                NSLog(@"Operations 1 and 2 waited on me, but now we're all finished!!!);
            };

            [operation2 addDependency:operation3];
            [queue addOperation:operation3];
        };

        [operation1 addDependency:operation2];
        [queue addOperation:operation2];
    };

    [queue addOperation:operation1];
}

自定义子类

您需要将 NSOperation 子类化才能使其工作。正如我提到的,您需要定义自己的完成块并确保在操作真正完成之前调用完成块,以便您可以添加依赖项。您可以将其添加到不同的块或委托方法中,而不是在新的完成块中添加依赖项。这样可以使我的示例简洁明了。

@interface Operation: NSOperation {
@property (nonatomic, copy) void (^almostFinished)(id result);
@end

@implementation Operation {
    //...

- (void)main {
    //...
    // Call here to allow to add dependencies and new ops
    self.almostFinished(result);  

    // Finish the op
    [self willChangeValueForKey:@"isFinished"];
    // repeat for isExecuting and do whatever else
    [self didChangeValueForKey:@"isFinished"];
}
@end

编辑:这不是最易读的东西,但它在一个方法中包含了所有代码。如果你想要花哨一些,那么就把事情放在委托方法中,或者在你定义这些事情的方式上发挥创意。

如果你想链式操作,但不喜欢嵌套,你可以使用NSOperation子类,然后定义你自己的完成处理程序:

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];

不过,坦率地说,我不确定这是否比嵌套方法更直观:

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];

顺便说一句,以上假设您熟悉子类化 NSOperation,尤其是创建异步 NSOperation 子类(并完成所有必要的 KVO)的微妙之处。如果您需要有关如何完成的示例,请告诉我。