学习NSBlockOperation

我是一个块的粉丝,但没有使用它们的并发性。 经过一番谷歌search之后,我把这个想法拼凑起来,把我在一个地方学到的所有东西都隐藏起来。 目标是在后台执行一个块,当它完成时,执行另一个块(如UIViewanimation)…

- (NSOperation *)executeBlock:(void (^)(void))block completion:(void (^)(BOOL finished))completion { NSOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:block]; NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{ completion(blockOperation.isFinished); }]; [completionOperation addDependency:blockOperation]; [[NSOperationQueue mainQueue] addOperation:completionOperation]; NSOperationQueue *backgroundOperationQueue = [[NSOperationQueue alloc] init]; [backgroundOperationQueue addOperation:blockOperation]; return blockOperation; } - (void)testIt { NSMutableString *string = [NSMutableString stringWithString:@"tea"]; NSString *otherString = @"for"; NSOperation *operation = [self executeBlock:^{ NSString *yetAnother = @"two"; [string appendFormat:@" %@ %@", otherString, yetAnother]; } completion:^(BOOL finished) { // this logs "tea for two" NSLog(@"%@", string); }]; NSLog(@"keep this operation so we can cancel it: %@", operation); } 

我的问题是:

  1. 它运作时,我运行它,但我错过了什么…隐藏的地雷? 我没有testing取消(因为我没有发明一个长期的操作),但是看起来它会工作吗?
  2. 我担心我需要限定我的backgroundOperation声明,以便我可以在完成块中引用它。 编译器不会抱怨,但是有潜伏期吗?
  3. 如果“串”是一个伊娃,如果我的键值在块运行时会发生什么? 或者在主线程上设置一个定时器并定期对其进行logging? 我能看到进展吗? 我会宣布它是primefaces吗?
  4. 如果这个工作正如我所期望的那样,那么这似乎是隐藏所有细节并获得并发性的好方法。 苹果为什么不给我写这个? 我错过重要的东西吗?

谢谢。

我不是在NSOperation或NSOperationQueues的专家,但我认为下面的代码是好一点,虽然我认为它仍有一些警告。 对于某些目的来说可能够用了,但并不是一个通用的并发解决scheme:

 - (NSOperation *)executeBlock:(void (^)(void))block inQueue:(NSOperationQueue *)queue completion:(void (^)(BOOL finished))completion { NSOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:block]; NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{ completion(blockOperation.isFinished); }]; [completionOperation addDependency:blockOperation]; [[NSOperationQueue currentQueue] addOperation:completionOperation]; [queue addOperation:blockOperation]; return blockOperation; } 

现在让我们使用它:

 - (void)tryIt { // Create and configure the queue to enqueue your operations backgroundOperationQueue = [[NSOperationQueue alloc] init]; // Prepare needed data to use in the operation NSMutableString *string = [NSMutableString stringWithString:@"tea"]; NSString *otherString = @"for"; // Create and enqueue an operation using the previous method NSOperation *operation = [self executeBlock:^{ NSString *yetAnother = @"two"; [string appendFormat:@" %@ %@", otherString, yetAnother]; } inQueue:backgroundOperationQueue completion:^(BOOL finished) { // this logs "tea for two" NSLog(@"%@", string); }]; // Keep the operation for later uses // Later uses include cancellation ... [operation cancel]; } 

一些你的问题的答案:

  1. 取消 。 通常你子类NSOperation所以你可以检查self.isCancelled并返回。 看到这个线程 ,这是一个很好的例子。 在当前的例子中,你不能检查是否你正在提供一个NSBlockOperation的块被取消,因为那时还没有这样的操作。 在块被调用时取消NSBlockOperation显然是可能的,但是很麻烦 。 NSBlockOperation适用于特定的简单情况。 如果你需要取消,你最好NSOperation 🙂

  2. 我在这里没有看到问题。 虽然注意两件事。 a)我改变了方法来运行当前队列中的完成块b)需要一个队列作为参数。 正如@Mike Weller所说,你应该更好地提供background queue所以你不需要为每个操作创build一个,并可以select使用什么队列来运行你的东西:)

  3. 我想是的,你应该使string atomic 。 有一件事你不应该忘记,如果你给队列提供了几个操作,它们可能不会按照这个顺序运行(必然),所以你最终可能会在你的string一个非常奇怪的消息。 如果你需要连续运行一个操作,你可以这样做: [backgroundOperation setMaxConcurrentOperationCount:1]; 在开始排队你的操作之前。 文档中有一个值得一读的说明:

    附加操作队列行为操作队列根据其优先级和准备情况执行其排队的操作对象。 如果所有排队的操作对象都具有相同的优先级,并且在将它们放入队列时准备好执行 – 也就是说,它们的isReady方法返回YES – 它们按照它们被提交到队列的顺序执行。 对于最大并发操作数设置为1的队列,这相当于一个串行队列。 但是,不应该依赖于操作对象的串行执行。 操作就绪的变化可以改变结果执行顺序。

  4. 我想在阅读这些知识之后,你会知道:)

你不应该为每个executeBlock:completion: call创build一个新的NSOperationQueue 。 这是昂贵的,并且此API的用户不能控制一次可以执行多less操作。

如果你正在返回NSOperation实例,那么你应该留给调用者来决定将它们添加到哪个队列。 但在这一点上,你的方法真的没有任何帮助,调用者也可以自己创buildNSBlockOperation

如果你只是想要一个简单而简单的方法在后台打散一个块,并在完成时执行一些代码,那么使用dispatch_*函数进行一些简单的GCD调用可能会更好。 例如:

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // do your background work // ... // now execute the 'completion' code. // you often want to dispatch back to the main thread to update the UI // For example: dispatch_async(dispatch_get_main_queue(), ^{ // update UI, etc. myLabel.text = @"Finished"; }); });