NSOperation 依赖和 completionBlock

NSOperation dependency and completionBlock

我们遇到了一个关于NSOperationQueue的简单问题,简单的操作逻辑如下:

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

NSOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"- Running operation A");
    [NSThread sleepForTimeInterval:1.2];
    NSLog(@"- Done operation A");
}];

NSOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"- Running operation B");
    [NSThread sleepForTimeInterval:2];
    NSLog(@"- Done operation B");
}];

[operationA setCompletionBlock:^{
    NSLog(@"-- Completion Block A");
}];

[operationB setCompletionBlock:^{
    NSLog(@"-- Completion Block B");
}];

[operationB addDependency:operationA];
[self.queue addOperations:@[operationA, operationB] waitUntilFinished:NO];

这是最终输出

2015-12-21 14:59:57.463 SampleProject[18046:310901] - Running operation A
2015-12-21 14:59:58.664 SampleProject[18046:310901] - Done operation A
2015-12-21 14:59:58.664 SampleProject[18046:310900] - Running operation B
2015-12-21 14:59:58.664 SampleProject[18046:310904] -- Completion Block A
2015-12-21 15:00:00.736 SampleProject[18046:310900] - Done operation B
2015-12-21 15:00:00.736 SampleProject[18046:310904] -- Completion Block B

正如我们所见,操作B操作A的completionBlock之前执行。在我们的实际应用中,我们有很多 operation A 而只有一个 operation B 依赖于所有 operation A.但是我们遇到的问题是 operationB 在最后一个 operation A 的完成块被调用之前启动,这通常会给信息操作 B.

如何让 操作 B 在所有 操作 A 的完成块之后执行?

为什么不能调用完成块a里面的操作,这就是完成块的作用。

[operationA setCompletionBlock:^{
    NSLog(@"-- Completion Block A");
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"- Running operation B");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"- Done operation B");
    }];
    [queue addOperations:@[operationB] waitUntilFinished:NO];
}];


[operationA setCompletionBlock:^{
    NSLog(@"-- Completion Block A when we dont need B");
}];

有一些更好的方法来执行此操作,而不是使用

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     [self operationB];
}

正如您在测试完成块中发现的那样,它们不是 'part of the queue',而是 运行 在操作队列之外(在另一个线程上)。因此,操作 A 的 completionBlock 将 运行 与操作 B 同时(大概)

我建议您重构代码以删除所有完成块。

你说你正在使用 completionBlocks 将信息从操作 A 传递到 B,对此有两种选择: 为 B 提供所有 A 的引用(不是弱的)所以当 B 运行s 它可以从所有 A 中选择结果。或者,如果由于某种原因无法在 B 运行s 之前保留所有 A,则将您的 completionBlock 重新创建为另一个 NSOperation:

NSOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
    // do stuff
}];

NSOperation *operationATail = [NSBlockOperation blockOperationWithBlock:^{
    // do completionBlock stuff
}];

[operationATail addDependency:operationA];
[operationB addDependency:operationATail];
[self.queue addOperations:@[operationA, operationATail, operationB] waitUntilFinished:NO];
  1. 避免完成块 - 它们 out-of-queue 机制不合适 用于同步任何操作或其通信。

  2. 引入依赖关系(B 依赖于 A)意味着 B 将仅在 A 成功完成后 运行。

  3. 出于这个原因 - 任何简单的数据对象都可以用来在这两个操作之间安全地“传递信息” - 可以简单地共享它(只要你在定义这两个操作的块之外创建它就足够了)操作。当“B”运行s时,它可以假设“A”已经将需要的信息放入数据对象中,并简单地访问它。

    self.queue = [[NSOperationQueue alloc] init];
    
    NSMutableDictionary *info = [NSMutableDictionary new]; // data object for communication
    
    NSOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"- Running operation A");
     [NSThread sleepForTimeInterval:1.2];
    
      info[@"DoThis"] = @YES;
      info[@"DoThat"] = @NO;
      NSLog(@"- Done operation A");
    }];
    
    NSOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
       NSLog(@"- Running operation B");
    
      if ([info[@"DoThis"] boolValue] NSLog(@"Op A said to do this.");
      if ([info[@"DoThat"] boolValue] NSLog(@"Op A said to do that.");
    
      [NSThread sleepForTimeInterval:2];
      NSLog(@"- Done operation B");
    }];
    
    [operationB addDependency:operationA];
    [self.queue addOperations:@[operationA, operationB] waitUntilFinished:NO];