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

Kor*_*nel 89 iphone queue notifications asynchronous nsoperation

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

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

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

Nic*_*rge 164

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

在你正在进行KVO的文件中的某个地方,为此声明KVO的上下文(更多信息):

static NSString *kQueueOperationsChanged = @"kQueueOperationsChanged";
Run Code Online (Sandbox Code Playgroud)

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

[self.queue addObserver:self forKeyPath:@"operations" options:0 context:&kQueueOperationsChanged];
Run Code Online (Sandbox Code Playgroud)

然后在你的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];
    }
}
Run Code Online (Sandbox Code Playgroud)

(这假设您的NSOperationQueue名字属于一个属性queue)

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

[self.queue removeObserver:self forKeyPath:@"operations" context:&kQueueOperationsChanged];
Run Code Online (Sandbox Code Playgroud)


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

  • 我认为你应该使用属性访问器,因为它提供了面向未来的封装(如果你决定例如懒惰地初始化队列).通过其ivar直接访问属性可能被认为是过早优化,但它实际上取决于确切的上下文.通过其ivar直接访问房产所节省的时间通常可以忽略不计,除非您每秒引用该属性超过100-1000次(作为一个令人难以置信的粗略估计). (26认同)
  • @NikolaiRuhe你是对的 - 当使用这个代码子类化它本身使用KVO在同一个`NSOperationQueue`对象上观察`operationCount`时,可能会导致错误,在这种情况下你需要正确使用context参数.它不太可能发生,但绝对可能.(拼出实际问题比添加snark +链接更有帮助) (19认同)
  • 找到了一个有趣的想法[这里](http://stackoverflow.com/questions/9998532/ios-how-to-know-when-nsoperationqueue-finish-processing-a-few-operations).我用它来创建NSOperationQueue的子类,添加了一个NSOperation属性'finalOpearation',它被设置为添加到队列的每个操作的依赖项.显然必须覆盖addOperation:这样做.还添加了一个协议,在finalOperation完成时将msg发送给委托.到目前为止一直在努力. (6认同)
  • 由于KVO使用不良而诱使其投票.这里描述的正确用法:http://www.dribin.org/dave/blog/archives/2008/09/24/proper_kvo_usage/ (2认同)

sof*_*ved 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>
Run Code Online (Sandbox Code Playgroud)

您应该知道,如果向队列添加了许多"短"操作,您可能会看到此行为(因为操作是作为添加到队列的一部分而启动的):

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

在我的项目中,我需要知道在将大量操作添加到串行NSOperationQueue(即maxConcurrentOperationCount = 1)之后并且仅在它们全部完成时才完成最后一次操作.

谷歌搜索我发现Apple开发人员回答"是一个连续的NSoperationQueue FIFO?"这个问题的声明. -

如果所有操作具有相同的优先级(在将操作添加到队列后未更改)并且所有操作始终为 - isReady == YES,那么它们被放入操作队列时,则串行NSOperationQueue为FIFO.

Chris Kane Cocoa Frameworks,Apple

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

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

:)


小智 17

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


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


Cal*_*sey 6

As of iOS 13.0, the operationCount and operation properties are deprecated. It's just as simple to keep track of the number of operations in your queue yourself and fire off a Notification when they've all completed. This example works with an asynchronous subclassing of Operation too.

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

Below is a subclass of Operation for easy asynchronous operations

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

}


Kri*_*ins 5

我就是这样做的.

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

myQueue = [[NSOperationQueue alloc] init];
[myQueue addObserver: self forKeyPath: @"operations" options: NSKeyValueObservingOptionNew context: NULL];
Run Code Online (Sandbox Code Playgroud)

......和观察者(在这种情况下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;
}
Run Code Online (Sandbox Code Playgroud)

在这个例子中,"微调器" UIActivityIndicatorView表示发生了某些事情.显然你可以换到适合......

  • 那个'for`循环似乎可能很昂贵(如果你一次取消所有操作怎么办?当队列被清理时,这不会得到二次性能吗?) (2认同)