学习NSBlockOperation

dan*_*anh 19 objective-c nsoperation nsoperationqueue ios 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);
}
Run Code Online (Sandbox Code Playgroud)

我的问题是:

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

谢谢.

nac*_*o4d 19

我不是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;
}
Run Code Online (Sandbox Code Playgroud)

现在让我们使用它:

- (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]; 
}
Run Code Online (Sandbox Code Playgroud)

您的问题的一些答案:

  1. 取消.通常你是NSOperation的子类,所以你可以先检查self.isCancelled并返回.看到这个帖子,这是一个很好的例子.在当前示例中,您无法检查是否已从要提供的块中取消操作,NSBlockOperation因为此时尚未执行此类操作.NSBlockOperation在调用块时取消s显然是可能的但是很麻烦.NSBlockOperations适用于特定的简单案例.如果您需要取消,您可以更好地继承NSOperation:)

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

  3. 我想是的,你应该做string atomic.你不应该忘记的一件事是,如果你向队列提供了几个操作,它们可能不会按顺序运行(必然),所以你最终可能会收到一条非常奇怪的消息string.如果您需要连续一次运行一个操作,则可以执行以下[backgroundOperation setMaxConcurrentOperationCount:1];操作:在开始排队操作之前.虽然文档中有一个值得阅读的注释:

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

  4. 我想在看完这些线后你知道:)


Mik*_*ler 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";
    });

});
Run Code Online (Sandbox Code Playgroud)