Answers:
使用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中仍然有效,因此对于向后兼容仍然有用。
operationCount
相同的NSOperationQueue
物体将可能导致错误,在这种情况下,你需要正确的使用方面的说法。这不太可能发生,但绝对有可能。(解决实际问题比添加snark和链接更为有用)
如果您期望(或期望)某种与该行为匹配的东西:
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)之后,以及仅在它们全部完成之后,上一次操作何时完成。
在谷歌搜索中,我找到了一名苹果开发人员的这一说法,以回应“是否是串行NSoperationQueue FIFO?”这一问题。-
如果所有操作都具有相同的优先级(在将操作添加到队列后不会更改),并且所有操作始终为-isReady == YES,直到它们被放入操作队列时,那么串行NSOperationQueue为FIFO。
克里斯·凯恩(Chris Kane)可可框架,苹果
在我的情况下,可以知道上一次操作何时添加到队列中。因此,在添加了最后一个操作之后,我向队列添加了另一个优先级较低的操作,该操作只发送队列已被清空的通知。根据苹果的声明,这可以确保仅在所有操作完成后才发送单个通知。
如果以不允许检测最后一个(即不确定性)的方式添加操作,那么我认为您必须采用上述KVO方法,并添加了额外的保护逻辑以尝试检测是否进一步可以添加操作。
:)
添加一个依赖于所有其他操作的NSOperation怎么样,这样它才能最后运行?
一种替代方法是使用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
});
这就是我的方法。
设置队列,并注册对operations属性的更改:
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
表示正在发生某种情况。显然,您可以更改以适合...
for
循环似乎很昂贵(如果您一次取消所有操作该怎么办?清理队列时不会获得二次性能?)
我正在使用类别来执行此操作。
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!)
}];
waitUntilFinished
应该是YES
从iOS 13.0开始,不建议使用operationCount和operation属性。自己跟踪队列中的操作数,并在完成所有操作后发出通知,就很简单。此示例也适用于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.")
}
}
decrementOperationCount()
方法在哪里调用?
使用KVO观察operationCount
队列的属性怎么样?然后您会在队列变空以及停止变空时听到有关它的信息。处理进度指示器可能就像执行以下操作一样简单:
[indicator setHidden:([queue operationCount]==0)]
NSOperationQueue
,3.1版抱怨它不符合KVO要求operationCount
。
operationCount
属性存在。
添加最后一个操作,例如:
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];
}
使用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!");
}
}];
仅供参考,您可以使用GCD dispatch_group在swift 3中实现此目的。所有任务完成后,您会收到通知。
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")
}
您可以创建一个new 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"];
}
如果您将此操作用作您的基类,则可以通过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")
}
没有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)
}
}