NSOperationQueue がすべてのタスクを完了したときに通知を受け取る
-
20-08-2019 - |
質問
NSOperationQueue
もっている waitUntilAllOperationsAreFinished
, 、しかし、それを同期的に待ちたくありません。キューが終了したときに UI で進行状況インジケーターを非表示にしたいだけです。
これを達成するための最良の方法は何でしょうか?
から通知を送信できません NSOperation
だって、どれが最後になるかわからないから、 [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 でも機能するため、下位互換性の点で依然として役立ちます。
他のヒント
もしあなたがこの動作を一致する何かを期待して(または希望)されます:
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です。
クリス・ケイン ココアフレームワーク、アップル
私の場合は最後の操作がキューに追加されたときに知ることができます。最後の操作が追加された後だから、私は何もしませんが、キューが空になっていた通知を送信優先度の低いキュー、に別の操作を追加します。 Appleの声明を考えると、これはすべての操作が完了した後にのみ、単一の通知が送信されることを保証します。
操作は、最後の1を検出することはできません方法で添加されている場合は、、(すなわち、非決定論的には)、私はあなたがしようとする追加KVOで行かなければならない追加のガードロジックで、前述したアプローチだと思いますさらに操作を加えることができるかどうかを検出する。
:)
どのようにそれが最後に実行されますので、他のすべてに依存しているNSOperationの追加について?
1つの選択肢は、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
何かが起こっていることを示しています。もちろん、あなたが合うように変更することができます...
KVOは、キューのoperationCount
性質を観察するために使用してはどう?キューが空に行ったとき、あなたはそれを聞くと思いますし、またそれが空で停止したとき。進行状況インジケータに対処するだけのようなものをやってと同じくらい簡単かもしれません。
[indicator setHidden:([queue operationCount]==0)]
のような最後の操作を追加します。
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!");
}
}];
FYI、あなたはの SWIFT 3 の中にのdispatch_groupの GCDでこれを達成することができます。すべてのタスクが終了したら、あなたは通知を受けることができます。
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")
}
これを行うためにカテゴリを使用しています。
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!)
}];
あなたは新しい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")
}
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)
}
}