学习NSBlockOperation

时间:2012-06-13 06:01:01

标签: objective-c ios nsoperation nsoperationqueue nsblockoperation

我是块的忠实粉丝,但没有将它们用于并发。经过一些谷歌搜索,我拼凑了这个想法,以隐藏我在一个地方学到的一切。目标是在后台执行一个块,当它完成时,执行另一个块(如UIView动画)......

- (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. 我跑的时候有效,但是我错过了什么......隐藏的地雷?我没有测试取消(因为我没有发明长时间的操作),但看起来它会起作用吗?
  2. 我担心我需要对backgroundOperation的声明进行限定,以便我可以在完成块中引用它。编译器没有抱怨,但潜伏在那里的保留周期?
  3. 如果“字符串”是一个ivar,如果我在块运行时键值观察到会发生什么?或者在主线程上设置一个计时器并定期记录它?我能看到进展吗?我会把它宣布为原子吗?
  4. 如果这样可以按照我的预期工作,那么它似乎是一种隐藏所有细节并获得并发性的好方法。 Apple为什么不为我写这个?我错过了一些重要的东西吗?
  5. 感谢。

3 个答案:

答案 0 :(得分:18)

我不是NSOperation或NSOperationQueues的专家,但我认为下面的代码有点好,虽然我认为它仍有一些警告。可能已经足够用于某些目的但不是并发的通用解决方案:

- (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并提前返回。见this thread,这是一个很好的例子。在当前示例中,您无法检查是否已从您提供的块中取消操作以生成NSBlockOperation,因为此时尚未执行此类操作。在调用块时取消NSBlockOperation显然是可能的,但cumbersomeNSBlockOperation用于特定的简单案例。如果您需要取消,则可以更好地继承NSOperation:)

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

  3. 我想是的,你应该string atomic。您不应忘记的一件事是,如果您向队列提供多个操作,它们可能无法以该顺序运行(必要),因此您可能会在string中收到一条非常奇怪的消息。如果您需要按顺序一次运行一个操作,则可以在开始排队操作之前执行:[backgroundOperation setMaxConcurrentOperationCount:1];docs中有一个值得阅读的注释:

      

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

  4. 我认为阅读完这些文字后你会知道:)

答案 1 :(得分:9)

您不应为每个NSOperationQueue电话创建新的executeBlock:completion:。这很昂贵,并且此API的用户无法控制一次可执行的操作数。

如果要返回NSOperation实例,则应将其留给调用者以决定将其添加到哪个队列。但是在那时,你的方法确实没有做任何有用的事情,调用者也可以自己创建NSBlockOperation

如果您只是想要一种简单易用的方法来在背景中分割一个块并在完成后执行一些代码,那么最好使用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";
    });

});

答案 2 :(得分:0)

无需设置要在完成时运行的块并添加这样的依赖项。像所有NSBlockOperation子类一样,NSOperation已经具有completionBlock属性,该属性将在块完成工作后自动运行:

@property(copy) void (^completionBlock)(void);

完成程序块进入finished状态时,运行该程序块。