NSOperationQueue
есть waitUntilAllOperationsAreFinished
, но я не хочу его синхронно ждать. Я просто хочу скрыть индикатор прогресса в пользовательском интерфейсе, когда очередь заканчивается.
Как лучше всего этого добиться?
Я не могу отправлять уведомления с моего NSOperation
s, потому что я не знаю, какое из них будет последним, и, [queue operations]
возможно, еще не пусто (или, что еще хуже, повторно заполнено), когда уведомление получено.
Ответы:
Используйте 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) и только тогда, когда все они были завершены.
Погуглил. Я нашел это заявление от разработчика Apple в ответ на вопрос "является ли серийный NSoperationQueue FIFO?" -
В моем случае можно узнать, когда последняя операция была добавлена в очередь. Поэтому после добавления последней операции я добавляю в очередь еще одну операцию с более низким приоритетом, которая ничего не делает, кроме отправки уведомления о том, что очередь была очищена. Учитывая заявление Apple, это гарантирует, что только одно уведомление будет отправлено только после завершения всех операций.
Если операции добавляются таким образом, который не позволяет обнаружить последний (т.е. недетерминированный), то я думаю, вам следует использовать подходы 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 });
источник
Вот как я это делаю.
Настройте очередь и зарегистрируйтесь для изменений в свойстве операций:
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
цикл кажется потенциально дорогим (что, если вы отмените все операции сразу? Разве это не приведет к квадратичной производительности при очистке очереди?)Начиная с 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()
метод?Для этого я использую категорию.
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
источник
waitUntilFinished
должен бытьYES
Как насчет использования 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") }
источник
Вы можете создать новый
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 {}
блок в OperationQueue :let queue = OOperationQueue() queue.addOperation(op) queue.addOperation(delayOp) queue.addExecution { finished in delay(0.5) { finished() } } queue.whenEmpty = { print("all operations finished") }
источник
Без КВО
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) } }
источник
Если вы пришли сюда в поисках решения с помощью комбайна - в итоге я просто слушал свой собственный объект состояния.
@Published var state: OperationState = .ready var sub: Any? sub = self.$state.sink(receiveValue: { (state) in print("state updated: \(state)") })
источник