19

我是块的忠实粉丝,但没有将它们用于并发。经过一番谷歌搜索,我拼凑出这个想法,将我学到的所有东西都隐藏在一个地方。目标是在后台执行一个块,完成后,执行另一个块(如 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 不为我写这个?我错过了什么重要的东西吗?

谢谢。

4

3 回答 3

21

我不是 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更早地检查并返回。看到这个线程,这是一个很好的例子。在当前示例中,您无法检查是否已从您提供的块中取消操作以进行操作,NSBlockOperation因为当时还没有这样的操作。在调用块时取消NSBlockOperations 显然是可能的,但很麻烦NSBlockOperations 用于特定的简单情况。如果您需要取消,则最好进行子类NSOperation化:)

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

  3. 我想是的,你应该做string atomic. 您不应该忘记的一件事是,如果您向队列提供多个操作,它们可能不会按该顺序运行(必然),因此您最终可能会在string. 如果您需要一次连续运行一项操作,您可以执行以下操作:[backgroundOperation setMaxConcurrentOperationCount:1];在开始对操作进行排队之前。不过,文档中有一个值得阅读的说明:

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

  4. 我想在阅读这些行之后你就知道了:)

于 2012-07-06T05:17:00.490 回答
10

您不应该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";
    });

});
于 2012-06-13T07:22:17.960 回答
0

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

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

完成块在其块移动到finished状态时运行。

于 2018-07-27T08:59:23.417 回答