web-dev-qa-db-ja.com

NSOperationQueueがすべてのタスクを完了したときに通知を受け取る

NSOperationQueueにはwaitUntilAllOperationsAreFinishedがありますが、それを同期的に待ちたくありません。キューが終了したときにUIで進行状況インジケーターを非表示にするだけです。

これを達成する最良の方法は何ですか?

NSOperationsから通知を送信できません。どの通知が最後になるかわからないためです。また、[queue operations]は、通知を受け取った時点でまだ空ではない(またはさらに悪いことに、再入力されている)場合があります。

89
Kornel

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];
    }
}

(これは、NSOperationQueuequeueという名前のプロパティにあると仮定しています)

オブジェクトの割り当てが完全に解除される前(またはキューの状態を気にするのをやめたとき)に、次のようにKVOから登録解除する必要があります。

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


補遺:iOS 4.0にはNSOperationQueue.operationCountプロパティがあり、ドキュメントによるとKVOに準拠しています。ただし、この回答はiOS 4.0でも機能するため、後方互換性のために引き続き役立ちます。

164
Nick Forge

この動作に一致する何かを期待している(または望んでいる)場合:

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です。

クリスケインココアフレームワーク、アップル

私の場合、最後の操作がいつキューに追加されたかを知ることができます。したがって、最後の操作が追加された後、キューに別の操作を追加します。これは優先度の低いもので、キューが空になったという通知を送信するだけです。 Appleの声明を考えると、これにより、すべての操作が完了した後にのみ単一の通知が送信されることが保証されます。

最後の操作を検出できない方法で操作が追加されている場合(つまり、非決定的)、上記のKVOアプローチを使用する必要があります。操作が追加される場合があります。

:)

20

最後に実行されるように、他のすべてに依存するNSOperationを追加してみませんか?

17
MostlyYes

1つの選択肢は、GCDを使用することです。参照として this を参照してください。

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
});
12
nhisyam

これが私のやり方です。

キューを設定し、操作プロパティの変更を登録します。

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です。明らかにあなたはスーツに変えることができます...

5
Kris Jenkins

次のような最後の操作を追加します。

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]; 

}
2
pvllnspk

KVOを使用してキューのoperationCountプロパティを監視するのはどうですか?その後、キューが空になったとき、および空でなくなったときにそれについて聞くでしょう。進行状況インジケーターの処理は、次のようなことをするのと同じくらい簡単です。

[indicator setHidden:([queue operationCount]==0)]
2
Sixten Otto

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!");
    }
}];
2
Stunner

これを行うためにカテゴリを使用しています。

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

2
brandonscript

参考までに、GCD dispatch_group in Swiftでこれを達成できます。すべてのタスクが完了すると通知を受け取ることができます。

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")
}

これを Operation を基本クラスとして使用すると、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")
}
0
user1244109

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)
    }
}
0
kasyanov-ms

新しい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"];
}
0
pgb