NSOperationQueue
持っていますがwaitUntilAllOperationsAreFinished
、私は同期的にそれを待ちたくありません。キューが終了したときにUIの進行状況インジケーターを非表示にしたいだけです。
これを達成するための最良の方法は何ですか?
NSOperation
どのsが最後になるのかわからないため[queue operations]
、通知を受信してもまだ空ではない(またはさらに悪いことに、再入力されている)ため、から通知を送信できません。
NSOperationQueue
持っていますがwaitUntilAllOperationsAreFinished
、私は同期的にそれを待ちたくありません。キューが終了したときにUIの進行状況インジケーターを非表示にしたいだけです。
これを達成するための最良の方法は何ですか?
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)、最後の操作がいつ完了したかを知る必要がありました。
グーグル「シリアル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
});
これが私のやり方です。
キューを設定し、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
ループは潜在的にコストがかかるようです(すべての操作を一度にキャンセルするとどうなりますか?キューがクリーンアップされているときに2次的なパフォーマンスが得られませんか?)
これを行うためにカテゴリを使用しています。
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の非同期サブクラス化でも機能します。
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!");
}
}];
参考までに、Swift 3の GCD dispatch_groupでこれを実現できます。すべてのタスクが完了したときに通知を受け取ることができます。
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")
}
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)
}
}