94

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

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

我无法从我NSOperation的 s 发送通知,因为我不知道哪一个会是最后一个,并且[queue operations]在收到通知时可能还不是空的(或更糟糕的是 - 重新填充)。

4

15 回答 15

167

使用 KVO 观察operations队列的属性,然后通过检查[queue.operations count] == 0.

在您正在执行 KVO 的文件中的某处,像这样声明 KVO 的上下文(更多信息):

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

在您的对象完全解除分配之前的某个时间点(或者当它停止关心队列状态时),您需要像这样从 KVO 注销:

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


附录:iOS 4.0 有一个NSOperationQueue.operationCount属性,根据文档,它是 KVO 兼容的。然而,这个答案在 iOS 4.0 中仍然有效,因此它对于向后兼容仍然有用。

于 2010-04-17T03:57:34.357 回答
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。

Chris Kane 可可框架,Apple

就我而言,可以知道最后一个操作何时添加到队列中。因此,在添加最后一个操作之后,我向队列中添加了另一个优先级较低的操作,它只发送队列已清空的通知。鉴于 Apple 的声明,这可确保仅在完成所有操作后才发送单个通知。

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

:)

于 2010-11-17T18:49:15.433 回答
18

添加一个依赖于所有其他的 NSOperation 以便它最后运行怎么样?

于 2009-09-07T17:11:47.303 回答
12

一种替代方法是使用 GCD。参考这个作为参考。

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
});
于 2013-05-08T10:03:57.507 回答
6

iOS 13.0开始,operationCountoperation属性已被弃用。自己跟踪队列中的操作数量并在它们全部完成时触发通知同样简单。此示例也适用于Operation的异步子类。

class MyOperationQueue: OperationQueue {
            
    public var numberOfOperations: Int = 0 {
        didSet {
            if numberOfOperations == 0 {
                print("All operations completed.")
                
                NotificationCenter.default.post(name: .init("OperationsCompleted"), object: nil)
            }
        }
    }
    
    public var isEmpty: Bool {
        return numberOfOperations == 0
    }
    
    override func addOperation(_ op: Operation) {
        super.addOperation(op)
        
        numberOfOperations += 1
    }
    
    override func addOperations(_ ops: [Operation], waitUntilFinished wait: Bool) {
        super.addOperations(ops, waitUntilFinished: wait)
        
        numberOfOperations += ops.count
    }
    
    public func decrementOperationCount() {
        numberOfOperations -= 1
    }
}

下面是 Operation 的子类,用于轻松进行异步操作

class AsyncOperation: Operation {
    
    let queue: MyOperationQueue

enum State: String {
    case Ready, Executing, Finished
    
    fileprivate var keyPath: String {
        return "is" + rawValue
    }
}

var state = State.Ready {
    willSet {
        willChangeValue(forKey: newValue.keyPath)
        willChangeValue(forKey: state.keyPath)
    }
    
    didSet {
        didChangeValue(forKey: oldValue.keyPath)
        didChangeValue(forKey: state.keyPath)
        
        if state == .Finished {
            queue.decrementOperationCount()
        }
    }
}

override var isReady: Bool {
    return super.isReady && state == .Ready
}

override var isExecuting: Bool {
    return state == .Executing
}

override var isFinished: Bool {
    return state == .Finished
}

override var isAsynchronous: Bool {
    return true
}

public init(queue: MyOperationQueue) {
    self.queue = queue
    super.init()
}

override func start() {
    if isCancelled {
        state = .Finished
        return
    }
    
    main()
    state = .Executing
}

override func cancel() {
    state = .Finished
}

override func main() {
    fatalError("Subclasses must override main without calling super.")
}

}

于 2020-02-03T18:56:16.833 回答
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表示正在发生某些事情。显然你可以改变以适应...

于 2009-11-15T19:48:02.447 回答
4

我正在使用一个类别来做到这一点。

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

于 2015-10-12T15:20:39.963 回答
2

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

[indicator setHidden:([queue operationCount]==0)]
于 2009-09-19T21:03:02.957 回答
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]; 

}
于 2012-10-09T19:36:08.377 回答
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!");
    }
}];
于 2015-12-29T10:19:53.380 回答
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")
}
于 2017-06-15T08:49:10.667 回答
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"];
}
于 2009-06-26T13:13:12.137 回答
0

如果你使用这个Operation作为你的基类,你可以将whenEmpty {}block 传递给OperationQueue

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

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

queue.whenEmpty = {
    print("all operations finished")
}
于 2017-04-19T00:18:14.473 回答
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)
    }
}
于 2018-04-09T04:29:39.240 回答
0

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

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

sub = self.$state.sink(receiveValue: { (state) in
 print("state updated: \(state)")
})
于 2019-12-30T19:19:31.340 回答