web-dev-qa-db-ja.com

NSOperationをサブクラス化して同時実行およびキャンセル可能にする

NSOperationをサブクラス化して並行処理を行い、キャンセルをサポートする方法に関する適切なドキュメントを見つけることができません。 Appleのドキュメントを読みましたが、「公式」の例を見つけることができません。

ここに私のソースコードがあります:

@synthesize isExecuting = _isExecuting;
@synthesize isFinished = _isFinished;
@synthesize isCancelled = _isCancelled;

- (BOOL)isConcurrent
{
    return YES;
}

- (void)start
{
/* WHY SHOULD I PUT THIS ?
    if (![NSThread isMainThread])
    {
        [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
        return;
    }
*/

    [self willChangeValueForKey:@"isExecuting"];
    _isExecuting = YES;
    [self didChangeValueForKey:@"isExecuting"];


    if (_isCancelled == YES)
    {
        NSLog(@"** OPERATION CANCELED **");
    }
    else
    {
        NSLog(@"Operation started.");
        sleep(1);
        [self finish];
    }
}

- (void)finish
{
    NSLog(@"operationfinished.");

    [self willChangeValueForKey:@"isExecuting"];
    [self willChangeValueForKey:@"isFinished"];

    _isExecuting = NO;
    _isFinished = YES;

    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];

    if (_isCancelled == YES)
    {
        NSLog(@"** OPERATION CANCELED **");
    }
}

私が見つけた例では、performSelectorOnMainThread:が使用されている理由がわかりません。これにより、操作が同時に実行されなくなります。

また、その行をコメントアウトすると、操作が同時に実行されます。ただし、isCancelledを呼び出した場合でも、cancelAllOperationsフラグは変更されません。

49
thierryb

さて、私はそれを理解しているので、あなたは2つの質問があります:

  1. コードのコメントに表示される_performSelectorOnMainThread:_セグメントが必要ですか?そのコードは何をしますか?

  2. この操作を含むcancelAllOperationsNSOperationQueueを呼び出しても__isCancelled_フラグが変更されないのはなぜですか?

これらを順番に処理しましょう。説明を簡単にするために、NSOperationのサブクラスはMyOperationと呼ばれると仮定します。あなたが誤解していることを説明してから、修正された例を示します。

1. NSOperationsを同時に実行する

ほとんどの場合、NSOperationsでNSOperationQueuesを使用し、コードから、それがあなたがしていることのように聞こえます。その場合、MyOperationsはバックグラウンドで操作を実行するように明示的に設計されているため、-(BOOL)isConcurrentメソッドが返すものに関係なく、NSOperationQueueは常にバックグラウンドスレッドで実行されます。 。

そのため、デフォルトでは_-[NSOperation start]_メソッドを呼び出すだけなので、通常_-main_メソッドをオーバーライドする必要はありません。それはあなたがオーバーライドすべきメソッドです。デフォルトの_-start_メソッドは、適切なタイミングでisExecutingisFinishedの設定を既に処理しています。

したがって、NSOperationをバックグラウンドで実行する場合は、_-main_メソッドをオーバーライドして、NSOperationQueueに配置するだけです。

コード内の_performSelectorOnMainThread:_により、MyOperationのすべてのインスタンスが常にメインスレッドでタスクを実行します。スレッド上で一度に実行できるコードは1つだけなので、これは他のMyOperationsを実行できないことを意味します。 NSOperationNSOperationQueueの全体的な目的は、バックグラウンドで何かをすることです。

メインスレッドに強制するのは、ユーザーインターフェイスを更新するときだけです。 MyOperationが終了したときにUIを更新する必要がある場合、thatは_performSelectorOnMainThread:_を使用する必要があります。以下の例で、その方法を示します。

2. NSOperationのキャンセル

_-[NSOperationQueue cancelAllOperations]_は_-[NSOperation cancel]_メソッドを呼び出します。これにより、その後の_-[NSOperation isCancelled]_の呼び出しがYESを返します。 ただし、これを無効にするために2つのことを行いました。

  1. NSOperationの_@synthesize isCancelled_メソッドをオーバーライドするために_-isCancelled_を使用しています。これを行う理由はありません。 NSOperationはすでに_-isCancelled_を完全に受け入れられる方法で実装しています。

  2. 独自の__isCancelled_インスタンス変数をチェックして、操作がキャンセルされたかどうかを判断しています。 NSOperationは、操作がキャンセルされた場合に_[self isCancelled]_がYESを返すことを保証します。 notは、カスタムセッターメソッドが呼び出されることを保証するものではなく、独自のインスタンス変数が最新であることも保証します。 _[self isCancelled]_を確認する必要があります

あなたがすべきこと

ヘッダー:

_// MyOperation.h
@interface MyOperation : NSOperation {
}
@end
_

そして実装:

_// MyOperation.m
@implementation MyOperation

- (void)main {
    if ([self isCancelled]) {
        NSLog(@"** operation cancelled **");
    }

    // Do some work here
    NSLog(@"Working... working....")

    if ([self isCancelled]) {
        NSLog(@"** operation cancelled **");
    }
    // Do any clean-up work here...

    // If you need to update some UI when the operation is complete, do this:
    [self performSelectorOnMainThread:@selector(updateButton) withObject:nil waitUntilDone:NO];

    NSLog(@"Operation finished");
}

- (void)updateButton {
    // Update the button here
}
@end
_

isExecutingisCancelled、またはisFinishedで何もする必要がないことに注意してください。これらはすべて自動的に処理されます。 _-main_メソッドをオーバーライドするだけです。とても簡単です。

(注意:技術的には、これは_-[MyOperation isConcurrent]_がNSOperationを上記で実装したように返すという意味で、「同時」NOではありません。ただし、バックグラウンドスレッドで実行されますisConcurrentメソッドは、メソッドの意図をより正確に説明するため、実際には_-willCreateOwnThread_という名前にする必要があります。 )

109
BJ Homer

@BJHomerの優れた答えは更新に値します。

同時操作は、startの代わりにmainメソッドをオーバーライドする必要があります。

Appleドキュメント に記載されているとおり:

並行操作を作成する場合は、少なくとも次のメソッドとプロパティをオーバーライドする必要があります。

  • start
  • asynchronous
  • executing
  • finished

適切な実装もrequirescancelをオーバーライドします。サブクラスthread-safeを作成し、必要なセマンティクスを正しく取得することも非常に注意が必要です。

したがって、完全かつ機能するサブクラスを Swiftで実装された提案 としてCode Reviewに配置しました。コメントや提案を歓迎します。

このクラスは、カスタム操作クラスの基本クラスとして簡単に使用できます。

4
CouchDeveloper

私はこれが古い質問であることを知っていますが、私は最近これを調査していて、同じ例に遭遇し、同じ疑問を持っていました。

すべての作業をmainメソッド内で同期的に実行できる場合、並行操作は必要ありません。startをオーバーライドすることも、作業を行って、終了したらmainから戻ることもできません。

ただし、ワークロードが本質的に非同期である場合、つまりNSURLConnectionをロードする場合は、サブクラスを開始する必要があります。 startメソッドが戻ったとき、操作はまだ完了していません。 NSOperationQueueは、isFinishedフラグとisExecutingフラグに手動でKVO通知を送信した場合(たとえば、非同期URLの読み込みが完了または失敗した場合)にのみ、完了と見なされます。

最後に、開始したい非同期ワークロードがメインスレッドでリッスンする実行ループを必要とする場合、メインスレッドに開始をディスパッチしたい場合があります。作業自体は非同期であるため、同時実行性は制限されませんが、ワーカースレッドで作業を開始すると、適切な実行ループの準備ができていない場合があります。

2
Rafael Nobre

cancelled」プロパティの定義について(または「_ cancelled」iVARを定義) NSOperationサブクラス内では、通常は必要ありません。単に、USERがキャンセルをトリガーするときに、カスタムコードは、作業がfinishedになったことをKVOオブザーバーに常に通知する必要があります。つまり、isCancelled => isFinished。

特に、NSOperationオブジェクトが他の操作オブジェクトの完了に依存している場合、それらのオブジェクトのisFinishedキーパスを監視します。したがって、終了通知(キャンセルが発生した場合(が発生した場合)の生成に失敗すると、アプリケーション内の他の操作の実行が妨げられる可能性があります。


ところで、@ BJ Homerの答え:「isConcurrentメソッドはnamed -willCreateOwnThreadである必要があります」とLOTが意味をなします

Start-methodをオーバーライドしない場合は、単にNSOperation-Objectのdefault-start-methodを手動で呼び出すだけなので、calling-thread自体はデフォルトで同期的です。そのため、NSOperation-Objectは非並行操作のみです。

ただし、start-method実装内でstart-methodをオーバーライドする場合、カスタムコードは別のスレッドなどを生成する必要があります。 「呼び出しスレッドのデフォルトは同期」という制限、したがってNSOperation-Objectを並行操作にすることで、その後非同期で実行できます。

0
J-Q

ASIHTTPRequest をご覧ください。これは、サブクラスとしてNSOperationの上に構築されたHTTPラッパークラスであり、これらを実装しているようです。 2011年半ばの時点で、開発者は新しいプロジェクトにASIを使用しないことを推奨していることに注意してください。

0
makdad