使用完成块嵌套方法

时间:2017-01-12 14:08:12

标签: objective-c grand-central-dispatch objective-c-blocks

我有几种方法具有以下结构:

- (void) doSomethingWithCompletion: (void (^)(NSError *error)) completion {
    __block NSError *fetchError = nil;

    dispatch_group_t dispatchGroup = dispatch_group_create();

    for (Item* item in self.items)
    {
        dispatch_group_enter(dispatchGroup);

        // fetchError = fetch online data
    }

    dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(),^{
        if (completion)
            completion(fetchError);
    });
}

我的目标是在彼此之后运行几个doSomethings,所以我可以这样:

[self doSomethingAWithCompletion: ^(NSArray *results NSError *error) {
   if (error == nil) {
      [self doSomethingBWithArray: results withCompletion: ^(NSError *error) {
         if (error == nil) {
            [self doSomethingCWithCompletion: ^(NSError *error) {
               if (error == nil) {
                   // done!!
               }
            }];
      }]; 
 }];

我正在努力的是第二个代码块(没有双关语);是嵌套所有方法,还是有其他解决方案?

重要的是,doSomethingBWithCompletion无法在doSomethingAWithCompletion完成之前开始,doSomethingCWithCompletion需要等到doSomethingBWithCompletion完成等等。

此外,doSomethingBWithCompletion使用doSomethingAWithCompletion等生成的数据

编辑:经过大量的思考,重构和简化我的代码之后,我最终只能使用嵌套方法完成了两个函数,如上所述,并使用{{1结果数组。

2 个答案:

答案 0 :(得分:1)

  

重要的是,doSomethingBWithCompletion在doSomethingAWithCompletion完成之前无法启动,doSomethingCWithCompletion需要等到doSomethingBWithCompletion完成等等。

根据评论:

  

块的结果不依赖于第一个结果不是吗?

并且

  

是的。例如,在第一个doSomething我确定哪些项目已过时,在第二个doSomething我下载并解析更新的项目,在第三个doSomething我将它们保存到商店。

(顺便说一下:你真的应该把这些信息添加到你的Q中。)

如果某个操作取决于上一个操作的结果(不仅是执行),则必须嵌套这些块。您的代码看起来不像这样,因为没有数据传递给完成块。

如果您没有这种依赖关系,则可以使用专用串行调度队列。但是,如果你有一个类似于持有从块到块传递的数据的管理器类,那么这也是你的情况下的解决方案。但这似乎是高度反感受的。

答案 1 :(得分:1)

社区可能会尝试将promises添加到objective-c中,这样会很高兴,因为这就是这里所需要的。如果没有提交到一个全新的库,你可以通过递归执行异步任务来处理嵌套(我同意这是一个无聊的事情......对于你的示例代码,这样的事情是这样的:

从没有参数并且产生数组的操作开始......

- (void)firstOpWithCompletion:(void (^)(NSArray *, NSError *))completion {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        NSArray *components = [@"this is an array of strings from the FIRST op" componentsSeparatedByString:@" "];
        if (completion) {
            completion(components, nil);
        }
    });
}

以下是一对采用数组参数并生成数组...

- (void)secondOpWithParam:(NSArray *)array completion:(void (^)(NSArray *, NSError *))completion {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        if (completion) {
            NSArray *components = [@"these strings are from the SECOND op" componentsSeparatedByString:@" "];
            NSArray *result = [array arrayByAddingObjectsFromArray:components];
            if (completion) {
                completion(result, nil);
            }
        }
    });
}

- (void)thirdOpWithParam:(NSArray *)array completion:(void (^)(NSArray *, NSError *))completion {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        if (completion) {
            NSArray *components = [@"these strings are from the THIRD op" componentsSeparatedByString:@" "];
            NSArray *result = [array arrayByAddingObjectsFromArray:components];
            if (completion) {
                NSLog(@"we did it.  returning %@", result);
                completion(result, nil);
            }
        }
    });
}

// ...as many as these as you need

现在,正如我在编辑之前的回答一样,我们最初只是在中间调用中添加一个参数传递...

- (void)doSeveralThingsInSequence:(NSArray *)todo param:(NSArray *)param {
    if (todo.count == 0) return;
    // you could generalize further here, by passing a "final" block and run that before the return

    NSString *nextTodo = todo[0];
    SEL sel = NSSelectorFromString(nextTodo);

    IMP imp = [self methodForSelector:sel];
    void (*func)(id, SEL, NSArray *, void (^)(NSArray *, NSError *)) = (void *)imp;
    func(self, sel, param, ^(NSArray *result, NSError *error) {
        if (!error) {
            NSArray *remainingTodo = [todo subarrayWithRange:NSMakeRange(1, todo.count-1)];
            [self doSeveralThingsInSequence:remainingTodo param:result];
        }
    });
}

单步执行代码:如果没有任何操作,此方法会失效,否则它会从传递的数组中获取下一个选择器名称,获取它的C函数实现并调用它,在启动的调用堆栈上放置一个完成块其余选择器的过程结束。

最后,doEverything调用第一个操作开始,然后开始运行一个操作列表(可以是一个任意长的列表),将数组输出从一个作为数组输入传递给下一个。 (您可以通过在链中传递id来进一步概括这一点

- (void)doEverything {
    [self firstOpWithCompletion:^(NSArray *array, NSError *error) {
        NSArray *todo = @[ @"secondOpWithParam:completion:", @"thirdOpWithParam:completion:" ];
        [self doSeveralThingsInSequence:todo param:array];
    }];
}

我完全按照发布的方式对此进行了测试,并看到了预期的输出:

(
this,
is,
an,
array,
of,
strings,
from,
the,
FIRST,
op,
these,
strings,
are,
from,
the,
SECOND,
op,
these,
strings,
are,
from,
the,
THIRD,
op
)