【问题标题】:Learning NSBlockOperation学习 NSBlockOperation
【发布时间】:2012-06-16 01:34:06
【问题描述】:

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

谢谢。

【问题讨论】:

  • 你考虑过使用 GCD 吗?或者这纯粹是一种学习练习?串行队列听起来与您正在寻找的完全一样。

标签: objective-c ios nsoperation nsoperationqueue nsblockoperation


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

    我不是 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,因为当时还没有这样的操作。在调用块时取消NSBlockOperations 显然是可能的,但cumbersomeNSBlockOperations 适用于特定的简单案例。如果你需要取消,你最好继承NSOperation :)

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

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

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

    4. 我想看完这些你就知道了:)

    【讨论】:

    • 优秀的答案。非常感谢。
    • 但不完全正确,恕我直言。对executeBlock的调用缺少队列参数
    • 对不起,如果这个问题很幼稚,但是为什么将“completionOperation”添加到“currentQueue”而不是“queue”?
    【解决方案3】:

    您不应该为每个executeBlock:completion: 调用创建一个新的NSOperationQueue。这很昂贵,而且此 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";
        });
    
    });
    

    【讨论】:

    • @danh ,关于取消东西请看广告 NSOperationsQueue 和方法 cancelAllOperations ;)
    猜你喜欢
    • 2016-06-09
    • 1970-01-01
    • 1970-01-01
    • 2015-05-01
    • 2013-03-24
    • 2020-10-22
    • 1970-01-01
    • 2020-12-19
    • 2021-10-17
    相关资源
    最近更新 更多