web-dev-qa-db-ja.com

NSURLSessionでNSOperationQueueを使用するにはどうすればよいですか?

私は、画像をダウンロードするためにその場でキューに追加することができるバルク画像ダウンローダーを構築しようとしています、そして、進行状況とダウンロードがいつ完了したかを知ることができます。

読んでみると、キュー機能のNSOperationQueueとネットワーク機能のNSURLSessionが私の最善策のように見えますが、この2つをタンデムで使用する方法については混乱しています。

NSOperationのインスタンスをNSOperationQueueに追加すると、キューに入れられます。そして、NSURLSessionDownloadTaskでダウンロードタスクを作成し、複数のタスクが必要な場合は複数作成しますが、2つをどのように組み合わせるかはわかりません。

NSURLSessionDownloadTaskDelegateは、ダウンロードの進行状況と完了の通知に必要なすべての情報を持っているようですが、特定のダウンロードを停止し、すべてのダウンロードを停止し、ダウンロードから返されるデータを処理する必要もあります。 。

53
Doug Smith

ここでのあなたの直感は正しいです。多くのリクエストを発行する場合、NSOperationQueueに4または5のmaxConcurrentOperationCountを指定すると非常に便利です。それがない場合、多くのリクエスト(たとえば50枚の大きな画像)を発行すると、低速のネットワーク接続(一部のセルラー接続など)で作業しているときにタイムアウトの問題が発生する可能性があります。操作キューにも他の利点(たとえば、依存関係、優先順位の割り当てなど)がありますが、同時実行の程度を制御することが主な利点です(IMHO)。

completionHandlerベースのリクエストを使用している場合、操作ベースのソリューションの実装は非常に簡単です(通常の同時NSOperationサブクラス実装です。同時実行の操作の設定セクションを参照してください) Operation Queues の章同時実行プログラミングガイドの詳細)。

ただし、delegateベースの実装を使用している場合、物事はかなり早く始まります。これは、タスクレベルのデリゲートがセッションレベルで実装されるNSURLSessionの理解可能な(ただし非常に迷惑な)機能のためです。 (それについて考えてみましょう:異なる処理を必要とする2つの異なる要求は、共有セッションオブジェクトで同じデリゲートメソッドを呼び出しています。Egad!)

デリゲートベースのNSURLSessionTaskを操作にラップすることはできますが(私や他の人が行ったと確信しています)、セッションオブジェクトに辞書を相互参照するタスク識別子を保持させるという扱いにくいプロセスが伴いますタスク操作オブジェクトでは、これらのタスクデリゲートメソッドをタスクオブジェクトに渡してから、タスクオブジェクトをさまざまなNSURLSessionTaskデリゲートプロトコルに準拠させます。 NSURLSessionはセッションでmaxConcurrentOperationCountスタイルの機能を提供しないため、かなりの量の作業が必要です(依存関係、完了など、他のNSOperationQueueの良さは言うまでもありません)ブロックなど)。

ただし、操作ベースの実装は、バックグラウンドセッションを備えた初心者向けではありません。アップロード/ダウンロードタスクは、アプリが終了した後も引き続き機能します(これは良いことです。これはバックグラウンドリクエストではかなり重要な動作です)が、アプリを再起動すると、操作キューとその操作がすべてなくなります。 。そのため、バックグラウンドセッションには、純粋なデリゲートベースのNSURLSession実装を使用する必要があります。

46
Rob

概念的には、NSURLSessionは操作キューです。 NSURLSessionタスクと完了ハンドラーのブレークポイントを再開すると、スタックトレースが非常に明らかになります。

NSURLSession に関する忠実なRay Wenderlichのチュートリアルからの抜粋です。完了ハンドラーの実行時にブレークポイントにNSLogステートメントが追加されています。

NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:londonWeatherUrl]
          completionHandler:^(NSData *data,
                              NSURLResponse *response,
                              NSError *error) {
            // handle response
            NSLog(@"Handle response"); // <-- breakpoint here       

  }] resume];

NSOperationQueue Serial Queue breakpoint

上記では、Thread 5 Queue: NSOperationQueue Serial Queueで実行されている完了ハンドラーを確認できます。

したがって、各NSURLSessionが独自の操作キューを維持し、セッションに追加された各タスクが内部でNSOperationとして実行されると推測します。したがって、NSURLSessionオブジェクトまたはNSURLSessionタスクを制御する操作キューを維持することは意味がありません。

NSURLSessionTask自体は、cancelresumesuspendなどの同等のメソッドをすでに提供しています。

独自のNSOperationQueueを使用する場合よりも制御が少ないことは事実です。ただし、NSURLSessionは新しいクラスであり、その目的は疑う余地なくその負担を軽減することです。

一番下の行:煩わしさを抑えたいが、制御を抑えたい場合、および信頼してAppleを代理してネットワークタスクを適切に実行するには、NSURLSessionを使用します。 NSURLConnectionと独自の操作キューを使用します。

38
Max MacLeod

更新:executingおよびfinishingプロパティは、現在のNSOperationのステータスに関する知識を保持します。 finishingYESに設定され、executingNOに設定されると、操作は完了したと見なされます。正しい処理方法はdispatch_groupを必要とせず、単に非同期NSOperationとして書くことができます:

  - (BOOL) isAsynchronous {
     return YES;
  }

  - (void) main
    {
       // We are starting everything
       self.executing = YES;
       self.finished = NO;

       NSURLSession * session = [NSURLSession sharedInstance];

       NSURL *url = [NSURL URLWithString:@"http://someurl"];

       NSURLSessionDataTask * dataTask = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){

          /* Do your stuff here */

         NSLog("Will show in second");

         self.executing = NO;
         self.finished = YES;
       }];

       [dataTask resume]
   }

asynchronousという用語は誤解を招くものであり、UI(メイン)スレッドとバックグラウンドスレッドの区別を意味するものではありません。

isAsynchronousYESに設定されている場合、これは、コードの一部がmainメソッドに関して非同期に実行されることを意味します。別の言い方をすると:非同期呼び出しがmainメソッド内で行われ、メインメソッドの終了後にメソッドが終了します

Apple os: https://speakerdeck.com/yageek/concurrency-on-darwin で並行性を処理する方法についてのスライドがあります。

古い回答dispatch_group_tを試すことができます。 GCDの保持カウンターとして考えることができます。

mainサブクラスのNSOperationメソッドで以下のコードを想像してください:

- (void) main
{

   self.executing = YES;
   self.finished = NO;

   // Create a group -> value = 0
   dispatch_group_t group = dispatch_group_create();

   NSURLSession * session = [NSURLSession sharedInstance];

   NSURL *url = [NSURL URLWithString:@"http://someurl"];

    // Enter the group manually -> Value = Value + 1
   dispatch_group_enter(group); ¨

   NSURLSessionDataTask * dataTask = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){


      /* Do your stuff here */

      NSLog("Will show in first");

      //Leave the group manually -> Value = Value - 1
      dispatch_group_leave(group);
   }];

   [dataTask resume];

  // Wait for the group's value to equals 0
  dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

  NSLog("Will show in second");

  self.executing = NO;
  self.finished = YES;
}
5
yageek

NSURLSessionを使用すると、手動でキューに操作を追加することはありません。 NSURLSessionでメソッド- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)requestを使用して、データタスクを生成し、それを開始します(resumeメソッドを呼び出します)。

操作キューを提供して、キューのプロパティを制御し、必要に応じて他の操作に使用することもできます。

データタスクで実行するNSOperationで実行する通常のアクション(開始、一時停止、停止、再開)のいずれか。

50個の画像をダウンロードするためにキューに入れるには、NSURLSessionが適切にキューに入れる50個のデータタスクを作成するだけです。

2
Reid Main

OperationQueueを使用しており、各操作で多数の同時ネットワーク要求を作成したくない場合は、各操作がキューに追加された後にqueue.waitUntilAllOperationsAreFinished()を呼び出すだけで済みます。以前の接続が完了した後にのみ実行されるようになり、同時ネットワーク接続の量が大幅に削減されます。

0
Above The Gods