web-dev-qa-db-ja.com

NSBlockOperationの学習

私はブロックの大ファンですが、並行性のためにそれらを使用していません。少しグーグルした後、私はこのアイデアをつなぎ合わせて、学んだことをすべて1か所に隠しました。目標は、バックグラウンドでブロックを実行し、それが終了したら、別のブロック(UIViewアニメーションなど)を実行することです...

- (NSOperation *)executeBlock:(void (^)(void))block completion:(void (^)(BOOL finished))completion {

    NSOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:block];

    NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
        completion(blockOperation.isFinished);
    }];

    [completionOperation addDependency:blockOperation];
    [[NSOperationQueue mainQueue] addOperation:completionOperation];    

    NSOperationQueue *backgroundOperationQueue = [[NSOperationQueue alloc] init];
    [backgroundOperationQueue addOperation:blockOperation];

    return blockOperation;
}

- (void)testIt {

    NSMutableString *string = [NSMutableString stringWithString:@"tea"];
    NSString *otherString = @"for";

    NSOperation *operation = [self executeBlock:^{
        NSString *yetAnother = @"two";
        [string appendFormat:@" %@ %@", otherString, yetAnother];
    } completion:^(BOOL finished) {
        // this logs "tea for two"
        NSLog(@"%@", string);
    }];

    NSLog(@"keep this operation so we can cancel it: %@", operation);
}

私の質問は次のとおりです。

  1. 実行すると動作しますが、何かが足りません...隠された地雷ですか?キャンセルをテストしていませんが(長い操作を発明していないため)、それは機能するように見えますか?
  2. 完了ブロックで参照できるように、backgroundOperationの宣言を修飾する必要があるのではないかと心配しています。コンパイラは文句を言いませんが、そこに潜んでいる保持サイクルはありますか?
  3. 「文字列」がivarの場合、ブロックの実行中にKey-Valueでそれを観察するとどうなりますか?または、メインスレッドにタイマーを設定して、定期的にログに記録しますか?進捗状況を確認できますか?アトミックと宣言しますか?
  4. これが期待どおりに機能する場合は、すべての詳細を非表示にして並行性を取得するための良い方法のようです。なぜApple私のためにこれを書かなかったのですか?私は何か重要なものが欠けていますか?

ありがとう。

19
danh

私はNSOperationまたはNSOperationQueuesの専門家ではありませんが、まだいくつかの注意点があると思いますが、以下のコードの方が少し優れていると思います。おそらくいくつかの目的には十分ですが、並行性の一般的な解決策ではありません。

- (NSOperation *)executeBlock:(void (^)(void))block
                      inQueue:(NSOperationQueue *)queue
                   completion:(void (^)(BOOL finished))completion
{
    NSOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:block];
    NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
        completion(blockOperation.isFinished);
    }];
    [completionOperation addDependency:blockOperation];

    [[NSOperationQueue currentQueue] addOperation:completionOperation];
    [queue addOperation:blockOperation];
    return blockOperation;
}

今それを使用しましょう:

- (void)tryIt
{
    // Create and configure the queue to enqueue your operations
    backgroundOperationQueue = [[NSOperationQueue alloc] init];

    // Prepare needed data to use in the operation
    NSMutableString *string = [NSMutableString stringWithString:@"tea"];
    NSString *otherString = @"for";

    // Create and enqueue an operation using the previous method
    NSOperation *operation = [self executeBlock:^{
        NSString *yetAnother = @"two";
        [string appendFormat:@" %@ %@", otherString, yetAnother];
    }
    inQueue:backgroundOperationQueue 
    completion:^(BOOL finished) {
        // this logs "tea for two"
        NSLog(@"%@", string);
    }];

    // Keep the operation for later uses
    // Later uses include cancellation ...
    [operation cancel]; 
}

あなたの質問に対するいくつかの答え:

  1. キャンセル。通常、NSOperationをサブクラス化して、self.isCancelledをチェックして先に戻ることができるようにします。 このスレッド を参照してください。これは良い例です。現在の例では、NSBlockOperationを作成するために指定しているブロックから操作がキャンセルされているかどうかを確認できません。その時点では、そのような操作はまだないためです。ブロックが呼び出されている間にNSBlockOperationsをキャンセルすることは明らかに可能ですが、 面倒NSBlockOperationsは、特定の簡単なケース用です。キャンセルが必要な場合は、NSOperationをサブクラス化することをお勧めします:)

  2. ここでは問題はありません。 2つのことに注意してください。 a)現在のキューで完了ブロックを実行するようにメソッドdoを変更しました。b)パラメーターとしてキューが必要です。 @Mike Wellerが言ったように、background queueを指定する方がよいでしょう。そうすれば、操作ごとに1つ作成する必要がなく、実行に使用するキューを選択できます:)

  3. はい、stringatomicを作成する必要があると思います。忘れてはならないことの1つは、キューに複数の操作を指定すると、それらが(必然的に)その順序で実行されない可能性があるため、stringに非常に奇妙なメッセージが表示される可能性があることです。一度に1つの操作を連続して実行する必要がある場合は、次の操作を実行できます。[backgroundOperation setMaxConcurrentOperationCount:1];操作のキューイングを開始する前。 docs にも、読む価値のあるメモがあります。

    追加の操作キューの動作操作キューは、優先度と準備状況に基づいて、キューに入れられた操作オブジェクトを実行します。キューに入れられたすべての操作オブジェクトの優先度が同じで、キューに入れられたときに実行する準備ができている場合、つまり、isReadyメソッドがYESを返す場合、それらはキューに送信された順序で実行されます。同時操作の最大数が1に設定されているキューの場合、これはシリアルキューに相当します。ただし、操作オブジェクトのシリアル実行に依存しないでください。操作の準備が変更されると、結果の実行順序が変更される可能性があります。

  4. 私はあなたが知っているこれらの行を読んだ後だと思います:)

19
nacho4d

executeBlock:completion:呼び出しごとに新しいNSOperationQueueを作成しないでください。これはコストがかかり、このAPIのユーザーは、一度に実行できる操作の数を制御できません。

NSOperationインスタンスを返す場合は、それらを追加するキューを決定するのは呼び出し元に任せる必要があります。しかし、その時点では、メソッドは実際には何の役にも立たず、呼び出し元はNSBlockOperationを自分で作成することもできます。

バックグラウンドでブロックをスピンオフし、終了時にコードを実行する簡単で簡単な方法が必要な場合は、dispatch_*関数を使用して簡単なGCD呼び出しを行う方がよいでしょう。例えば:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // do your background work
    // ...

    // now execute the 'completion' code.
    // you often want to dispatch back to the main thread to update the UI
    // For example:

    dispatch_async(dispatch_get_main_queue(), ^{
        // update UI, etc.
        myLabel.text = @"Finished";
    });

});
9
Mike Weller

完了時に実行するブロックを設定して、このような依存関係を追加する必要はありません。 NSBlockOperationすべてのNSOperationサブクラスと同様に、すでに completionBlock プロパティがあり、ブロックが作業を終了すると自動的に実行されます。

@property(copy) void (^completionBlock)(void);

完了ブロックは、そのブロックがfinished状態に移行したときに実行されます。

0
nevan king