2

私は多くの URL リクエスト (約 60 個の小さな画像) を実行しており、それらを非同期的に実行し始めました。私のコードは別の画像を追加し(ダウンロードはほとんどありません)、リクエストを設定します。

リクエストが完了したら、最初に追加された場所に「データ」を配置したいのですが、画像を正しい場所に保存するために「imageLocation」をブロックに渡す方法がわかりません。

3行目を以下に置き換えましたが、これはうまくいくようですが、100%正しいわけではありません(画像がほぼ同じであるため、見分けるのは非常に困難です)。また、ブロックを宣言したところで「imageLocation」を渡すことも考えています。

これを確認できますか?

__block int imageLocation = [allImages カウント] - 1;

  // Add another image to MArray
  [allImages addObject:[UIImage imageNamed:@"downloading.png"]];
  imageLocation = [allImages count] - 1;

  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
            [request setTimeoutInterval: 10.0];
            [NSURLConnection sendAsynchronousRequest:request
            queue:[NSOperationQueue currentQueue]
            completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {

                     if (data != nil && error == nil)
                     {
                         //All Worked
                         [allImages replaceObjectAtIndex:imageLocation withObject:[UIImage imageWithData:data]];

                     }
                     else
                     {
                         // There was an error, alert the user
                         [allImages replaceObjectAtIndex:imageLocation withObject:[UIImage imageNamed:@"error.png"]];

         }];
4

2 に答える 2

1

非同期メソッドを扱うのは苦痛です ;)

あなたの場合、完了ブロックが指定されたキューで実行されることが保証されています。ただし、キューの最大同時操作数が 1 であることを確認する必要があります。そうでない場合、共有リソースへの同時アクセスは安全ではありません。これは古典的なレースhttp://en.wikipedia.org/wiki/Race_conditionです。NSOperationQueue の最大同時操作は、プロパティで設定できます。

一般に、特に指定がない限り、完了ハンドラは任意のスレッドで実行できます。

「Promises」http://en.wikipedia.org/wiki/Promise_(​​programming)と呼ばれる概念を使用すると、非同期メソッドの処理がはるかに簡単になります。基本的に、「プロミス」は将来評価される結果を表しますが、プロミス自体はすぐに利用できます。同様の概念は「先物」または「据え置き」と呼ばれます。

GitHub の Objective-C には、RXPromise というプロミスの実装があります。これを使用すると、ハンドラー ブロック内から共有リソースに安全にアクセスすることもできます。実装は次のようになります。

-(RXPromise*) fetchImageFromURL:(NSString*)urlString queue:(NSOperationQueue*) queue
{
    @autoreleasepool {
        RXPromise* promise = [[RXPromise alloc] init];

        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
        [NSURLConnection sendAsynchronousRequest:request
                                           queue:queue
                               completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
                                   if (data != nil) {
                                       [promise fulfillWithValue:data];                                   
                                   }
                                   else { // There was an error
                                       [promise rejectWithReason:error];
                                   };
                               }];
        return promise;
    }
}

次に、それを呼び出します。

- (void) fetchImages {

    ...

    for (NSUInteger index = 0; index < N; ++index)
    {
        NSString* urlString = ...
        [self fetchImageFromURL:urlString, self.queue]
        .then(^id(id data){
            [self.allImages replaceObjectAtIndex:index withObject:[UIImage imageWithData:data]];
            return @"OK";
        },
        ^id(NSError* error) {
            [self.allImages replaceObjectAtIndex:index withObject:[UIImage imageNamed:@"error.png"]];
            return error;
        });  
    }
}
于 2013-05-24T17:37:48.487 に答える
1

いくつかの考え:

  1. 60 個の画像をダウンロードする場合、ダウンロードにシリアル キューを使用することはお勧めしません (たとえば、 of でオペレーション キューを使用しないmaxConcurrentOperationCountでください1)。代わりに、同時キューを使用します。コードがスレッドセーフであることを確認するために更新を同期する必要があります (これは、モデルの最終更新のために更新をメイン キューなどのシリアル キューにディスパッチすることで簡単に実行できます)。ダウンロード自体にシリアル キューを使用することはお勧めしません。

  2. 便利なメソッドを使用する場合はNSURLConnection、次の同時操作要求アプローチのようなものをお勧めします (バックグラウンド キューにあるため、 ではなく を使用していますsendSynchronousRequest) sendAsynchronousRequest。画像の URL のオブジェクト:NSArrayimageURLsNSURL

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 4;
    
    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
    
    NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"all done %.1f", CFAbsoluteTimeGetCurrent() - start);
        NSLog(@"allImages=%@", self.allImages);
    }];
    
    [imageURLs enumerateObjectsUsingBlock:^(NSURL *url, NSUInteger idx, BOOL *stop) {
        NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            NSURLResponse *response = nil;
            NSError *error = nil;
            NSData *data = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] returningResponse:&response error:&error];
            if (!data) {
                NSLog(@"%s sendSynchronousRequest error: %@", __FUNCTION__, error);
            } else {
                UIImage *image = [UIImage imageWithData:data];
                if (image) {
                    dispatch_sync(dispatch_get_main_queue(), ^{
                        [self.allImages replaceObjectAtIndex:idx withObject:image];
                    });
                }
            }
        }];
        [queue addOperation:operation];
        [completionOperation addDependency:operation];
    }];
    [[NSOperationQueue mainQueue] addOperation:completionOperation];
    

    いくつかの余談: まず、同時実行の程度を制限できることが重要であるため、GCD 同時実行キューではなく操作キューを使用しています。次に、完了操作を追加しました。これは、すべてのダウンロードがいつ完了したかを知ることが役立つと思われるためですが、それが必要ない場合、コードは明らかに単純です。第 3 に、 を使用したベンチマーク コードは不要ですが、 と を使用してパフォーマンスを比較CFAbsoluteTimeする場合にのみ診断目的で役立ちます。maxConcurrentOperationCount41

  3. NSURLConnection上記の便利な方法を使用するよりも、NSOperationベースのネットワーク リクエストを使用することをお勧めします。独自に作成することも、AFNetworkingなどの実績のあるソリューションを使用することもできます。それは次のようになります。

    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
    
    NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"all done %.1f", CFAbsoluteTimeGetCurrent() - start);
        NSLog(@"allImages=%@", self.allImages);
    }];
    
    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    manager.responseSerializer = [AFImageResponseSerializer serializer];
    manager.operationQueue.maxConcurrentOperationCount = 4;
    
    [imageURLs enumerateObjectsUsingBlock:^(NSURL *url, NSUInteger idx, BOOL *stop) {
        NSOperation *operation = [manager GET:[url absoluteString] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
            [self.allImages replaceObjectAtIndex:idx withObject:responseObject];
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            NSLog(@"%s image request error: %@", __FUNCTION__, error);
        }];
        [completionOperation addDependency:operation];
    }];
    [[NSOperationQueue mainQueue] addOperation:completionOperation];
    

    AFNetworking はこれらの完了ブロックをメイン キューに送り返すため、同時ネットワーク リクエストを処理しながら、同期の問題を解決します。

    しかし、ここでの重要なメッセージは、NSOperationベースのネットワーク リクエスト (またはメソッドを使用する少なくとも 1 つNSURLConnectionDataDelegate) によって追加の機会が開かれるということです (たとえば、必要に応じてこれらのネットワーク リクエストをすべてキャンセルしたり、進行状況の更新を取得したりできます)。 )。

  4. 率直に言って、事前に画像を効率的にダウンロードする方法を説明する 2 つのソリューションを見てきたので、これは本質的に非効率的なプロセスであることを指摘せざるを得ません。ジャストインタイム (別名「遅延」) の方法で、必要に応じて画像を非同期に要求する「遅延」画像読み込みプロセスをお勧めします。これに対する最も簡単な解決策は、 AFNetworkingSDWebImageUIImageViewによって提供されるようなカテゴリを使用することです。(既に他の目的で AFNetworking を使用している場合は、AFNetworking を使用しますが、SDWebImage のカテゴリはもう少し強力だと思います。) これらは画像を非同期でシームレスにロードするだけでなく、キャッシュなどの他の多くの利点を提供します。効率的なメモリ使用など。UIImageView

    [imageView setImageWithURL:url placeholder:[UIImage imageNamed:@"placeholder"]];
    

ネットワーク要求を効率的に実行するためのいくつかの考え。それが役立つことを願っています。

于 2013-11-11T04:37:45.050 に答える