当NSOperationQueue完成所有任务时获取通知

时间:2009-06-26 13:00:14

标签: iphone asynchronous queue notifications nsoperation

NSOperationQueuewaitUntilAllOperationsAreFinished,但我不想同步等待它。我只想在队列完成时隐藏UI中的进度指示器。

实现这一目标的最佳方法是什么?

我无法发送来自NSOperation的通知,因为我不知道哪一个会是最后一个,[queue operations]可能不会是空的(或者更糟 - 重新填充)收到通知。

15 个答案:

答案 0 :(得分:164)

使用KVO观察队列的operations属性,然后通过检查[queue.operations count] == 0来判断队列是否已完成。

在你正在进行KVO的文件中,为此声明KVO的上下文(more info):

static NSString *kQueueOperationsChanged = @"kQueueOperationsChanged";

设置队列时,请执行以下操作:

[self.queue addObserver:self forKeyPath:@"operations" options:0 context:&kQueueOperationsChanged];

然后在observeValueForKeyPath

中执行此操作
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object 
                         change:(NSDictionary *)change context:(void *)context
{
    if (object == self.queue && [keyPath isEqualToString:@"operations"] && context == &kQueueOperationsChanged) {
        if ([self.queue.operations count] == 0) {
            // Do something here when your queue has completed
            NSLog(@"queue has completed");
        }
    }
    else {
        [super observeValueForKeyPath:keyPath ofObject:object 
                               change:change context:context];
    }
}

(假设您的NSOperationQueue位于名为queue

的属性中

在你的对象完全deallocs之前的某个时刻(或当它停止关心队列状态时),你需要从KVO取消注册,如下所示:

[self.queue removeObserver:self forKeyPath:@"operations" context:&kQueueOperationsChanged];


附录:iOS 4.0具有NSOperationQueue.operationCount属性,根据文档符合KVO标准。这个答案仍然适用于iOS 4.0,因此它对于向后兼容仍然有用。

答案 1 :(得分:20)

如果您期望(或希望)符合此行为的内容:

t=0 add an operation to the queue.  queueucount increments to 1
t=1 add an operation to the queue.  queueucount increments to 2
t=2 add an operation to the queue.  queueucount increments to 3
t=3 operation completes, queuecount decrements to 2
t=4 operation completes, queuecount decrements to 1
t=5 operation completes, queuecount decrements to 0
<your program gets notified that all operations are completed>

您应该知道,如果将一些“短”操作添加到队列中,您可能会看到此行为(因为操作是作为添加到队列的一部分启动的):

t=0  add an operation to the queue.  queuecount == 1
t=1  operation completes, queuecount decrements to 0
<your program gets notified that all operations are completed>
t=2  add an operation to the queue.  queuecount == 1
t=3  operation completes, queuecount decrements to 0
<your program gets notified that all operations are completed>
t=4  add an operation to the queue.  queuecount == 1
t=5  operation completes, queuecount decrements to 0
<your program gets notified that all operations are completed>

在我的项目中,我需要知道在将大量操作添加到序列号NSOperationQueue(即maxConcurrentOperationCount = 1)之后,并且只有在它们全部完成后才完成最后一次操作。

谷歌搜索我发现Apple开发人员回答“是一个连续的NSoperationQueue FIFO?”的问题。 -

  

如果所有操作都具有相同的优先级(之后没有更改)   操作被添加到队列中)并且所有操作始终是 -   isReady == YES在它们被放入操作队列时,然后是一个序列   NSOperationQueue是FIFO。

     克里斯凯恩   Cocoa Frameworks,Apple

在我的情况下,可以知道上次操作何时被添加到队列中。因此,在添加最后一个操作之后,我将另一个操作添加到队列中,优先级较低,除了发送队列已清空的通知之外什么都不做。鉴于Apple的声明,这确保了仅在所有操作完成后才发送一个通知。

如果以不允许检测最后一个的方式添加操作(即,非确定性),那么我认为你必须采用上面提到的KVO方法,并添加额外的保护逻辑以尝试检测是否可以添加进一步的操作。

:)

答案 2 :(得分:17)

如何添加依赖于所有其他NSOperation的NSOperation,以便它最后运行?

答案 3 :(得分:12)

另一种方法是使用GCD。请参考this作为参考。

dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group,queue,^{
 NSLog(@"Block 1");
 //run first NSOperation here
});

dispatch_group_async(group,queue,^{
 NSLog(@"Block 2");
 //run second NSOperation here
});

//or from for loop
for (NSOperation *operation in operations)
{
   dispatch_group_async(group,queue,^{
      [operation start];
   });
}

dispatch_group_notify(group,queue,^{
 NSLog(@"Final block");
 //hide progress indicator here
});

答案 4 :(得分:5)

我就是这样做的。

设置队列,并注册操作属性中的更改:

myQueue = [[NSOperationQueue alloc] init];
[myQueue addObserver: self forKeyPath: @"operations" options: NSKeyValueObservingOptionNew context: NULL];

...并且观察者(在这种情况下为self)实现:

- (void) observeValueForKeyPath:(NSString *) keyPath ofObject:(id) object change:(NSDictionary *) change context:(void *) context {

    if (
        object == myQueue
        &&
        [@"operations" isEqual: keyPath]
    ) {

        NSArray *operations = [change objectForKey:NSKeyValueChangeNewKey];

        if ( [self hasActiveOperations: operations] ) {
            [spinner startAnimating];
        } else {
            [spinner stopAnimating];
        }
    }
}

- (BOOL) hasActiveOperations:(NSArray *) operations {
    for ( id operation in operations ) {
        if ( [operation isExecuting] && ! [operation isCancelled] ) {
            return YES;
        }
    }

    return NO;
}

在此示例中,“微调器”是UIActivityIndicatorView,表示正在发生某些事情。显然你可以换到适合......

答案 5 :(得分:4)

iOS 13.0 开始,operationCountoperation属性已弃用。自己跟踪队列中的操作数量并在完成所有操作后发出 Notifications (通知)就很简单。此示例也适用于 Operation 的异步子类。

<svg width="200px" viewBox="0 0 200 200">

  <rect width="200" height="200" fill="red" />
  
  <svg width="200px" viewBox="0 0 200 200">
  
    <rect width="100" height="100" fill="yellow" />
  
    <svg width="200px" viewBox="0 0 200 200">
    
      <rect width="50" height="50" fill="red" />
    
      <svg width="200px" viewBox="0 0 200 200">
      
        <rect width="25" height="25" fill="yellow" />
      
        <svg width="200px" viewBox="0 0 200 200">

          <rect width="12.5" height="12.5" fill="red" />

        </svg>
      </svg>
    </svg>
  </svg>
</svg>

下面是Operation的子类,用于简单的异步操作

SELECT 
     e.Id
    ,e.EmployeeNumber
    ,e.LastName
    ,e.FirstName
    ,ec.CheckNumber
    ,ec.VoidDT
    ,ec.MaxDt AS FirstChecktDT
FROM
FROM PR_Employee e
JOIN (
    SELECT 
         CheckNumber
        ,VoidDT
        ,EmployeeId
        ,MAX(CheckDT) AS MaxDt
    FROM PR_Employeecheck
    WHERE CheckNumber IS NOT NULL
    ) AS ec 
    ON ec.EmployeeId = e.id 
WHERE e.employeestatusid IN (1,4)
ORDER BY employeenumber
    ,CheckDT DESC;

}

答案 6 :(得分:2)

使用KVO观察队列的operationCount属性怎么样?然后,当队列变空时,以及当它停止为空时,你会听到它。处理进度指示器可能就像执行以下操作一样简单:

[indicator setHidden:([queue operationCount]==0)]

答案 7 :(得分:2)

添加上一个操作,如:

NSInvocationOperation *callbackOperation = [[NSInvocationOperation alloc] initWithTarget:object selector:selector object:nil];

所以:

- (void)method:(id)object withSelector:(SEL)selector{
     NSInvocationOperation *callbackOperation = [[NSInvocationOperation alloc] initWithTarget:object selector:selector object:nil];
     [callbackOperation addDependency: ...];
     [operationQueue addOperation:callbackOperation]; 

}

答案 8 :(得分:2)

使用ReactiveObjC我发现这很有效:

// skip 1 time here to ignore the very first call which occurs upon initialization of the RAC block
[[RACObserve(self.operationQueue, operationCount) skip:1] subscribeNext:^(NSNumber *operationCount) {
    if ([operationCount integerValue] == 0) {
         // operations are done processing
         NSLog(@"Finished!");
    }
}];

答案 9 :(得分:1)

我正在使用某个类别来执行此操作。

<强> NSOperationQueue + Completion.h

//
//  NSOperationQueue+Completion.h
//  QueueTest
//
//  Created by Artem Stepanenko on 23.11.13.
//  Copyright (c) 2013 Artem Stepanenko. All rights reserved.
//

typedef void (^NSOperationQueueCompletion) (void);

@interface NSOperationQueue (Completion)

/**
 * Remarks:
 *
 * 1. Invokes completion handler just a single time when previously added operations are finished.
 * 2. Completion handler is called in a main thread.
 */

- (void)setCompletion:(NSOperationQueueCompletion)completion;

@end

<强> NSOperationQueue + Completion.m

//
//  NSOperationQueue+Completion.m
//  QueueTest
//
//  Created by Artem Stepanenko on 23.11.13.
//  Copyright (c) 2013 Artem Stepanenko. All rights reserved.
//

#import "NSOperationQueue+Completion.h"

@implementation NSOperationQueue (Completion)

- (void)setCompletion:(NSOperationQueueCompletion)completion
{
    NSOperationQueueCompletion copiedCompletion = [completion copy];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self waitUntilAllOperationsAreFinished];

        dispatch_async(dispatch_get_main_queue(), ^{
            copiedCompletion();
        });
    });
}

@end

<强>用法

NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    // ...
}];

NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    // ...
}];

[operation2 addDependency:operation1];

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation1, operation2] waitUntilFinished:YES];

[queue setCompletion:^{
    // handle operation queue's completion here (launched in main thread!)
}];

来源:https://gist.github.com/artemstepanenko/7620471

答案 10 :(得分:1)

仅供参考,您可以使用 swift 3 中的GCD dispatch_group 来实现此目的。所有任务完成后,您都会收到通知。

let group = DispatchGroup()

    group.enter()
    run(after: 6) {
      print(" 6 seconds")
      group.leave()
    }

    group.enter()
    run(after: 4) {
      print(" 4 seconds")
      group.leave()
    }

    group.enter()
    run(after: 2) {
      print(" 2 seconds")
      group.leave()
    }

    group.enter()
    run(after: 1) {
      print(" 1 second")
      group.leave()
    }


    group.notify(queue: DispatchQueue.global(qos: .background)) {
      print("All async calls completed")
}

答案 11 :(得分:0)

您可以创建新的NSThread,或在后台执行选择器,然后在那里等待。 NSOperationQueue完成后,您可以发送自己的通知。

我正在考虑类似的事情:

- (void)someMethod {
    // Queue everything in your operationQueue (instance variable)
    [self performSelectorInBackground:@selector(waitForQueue)];
    // Continue as usual
}

...

- (void)waitForQueue {
    [operationQueue waitUntilAllOperationsAreFinished];
    [[NSNotificationCenter defaultCenter] postNotification:@"queueFinished"];
}

答案 12 :(得分:0)

如果您使用此Operation作为基类,则可以将whenEmpty {}块传递给OperationQueue

let queue = OOperationQueue()
queue.addOperation(op)
queue.addOperation(delayOp)

queue.addExecution { finished in
    delay(0.5) { finished() }
}

queue.whenEmpty = {
    print("all operations finished")
}

答案 13 :(得分:0)

没有KVO

private let queue = OperationQueue()

private func addOperations(_ operations: [Operation], completionHandler: @escaping () -> ()) {
    DispatchQueue.global().async { [unowned self] in
        self.queue.addOperations(operations, waitUntilFinished: true)
        DispatchQueue.main.async(execute: completionHandler)
    }
}

答案 14 :(得分:0)

如果您到这里来寻找使用Combine的解决方案-我最终只是听我自己的状态对象。

@Published var state: OperationState = .ready
var sub: Any?

sub = self.$state.sink(receiveValue: { (state) in
 print("state updated: \(state)")
})