52

並行してサブクラス化する方法と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

4

6 に答える 6

111

さて、私が理解しているように、あなたには2つの質問があります:

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

  2. この操作を含むを呼び出した_isCancelledときにフラグが変更されないのはなぜですか?cancelAllOperationsNSOperationQueue

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

1. NSOperations を同時に実行する

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

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

したがって、 をバックグラウンドで実行したい場合は、単にメソッドをNSOperationオーバーライドして.-mainNSOperationQueue

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

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

2. NSOperation のキャンセル

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

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

  2. 独自のインスタンス変数をチェックして_isCancelled、操作がキャンセルされたかどうかを判断しています。操作がキャンセルされた場合に返さNSOperationれる保証。カスタム セッター メソッドが呼び出されることも、独自のインスタンス変数が最新であることも保証されません。チェックしているはずです[self isCancelled]YES[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メソッドをオーバーライドするだけです。それはとても簡単です。

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

于 2010-11-29T04:24:44.110 に答える
5

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

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

Apple Documentationに記載されているように:

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

  • start
  • asynchronous
  • executing
  • finished

適切な実装には、オーバーライドも必要cancelです。サブクラスをスレッドセーフにし、必要なセマンティクスを正しくすることも非常に難しい作業です。

したがって、コード レビューで Swift に実装された提案として、完全で機能するサブクラスを配置しました。コメントや提案は大歓迎です。

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

于 2015-10-15T11:42:07.137 に答える
2

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

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

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

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

于 2013-10-18T14:06:37.287 に答える
0

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

于 2010-11-28T21:23:26.050 に答える
-2

このブログ投稿:

http://www.dribin.org/dave/blog/archives/2009/09/13/snowy_concurrent_operations/

必要な理由を説明します。

if (![NSThread isMainThread])
{
    [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
    return;
}

あなたのstart方法で。

于 2012-10-01T22:15:52.420 に答える