複数の非同期NSURLConnection接続の管理


88

私のクラスには、次のような大量の繰り返しコードがあります。

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
                                                              delegate:self];

非同期リクエストの問題は、さまざまなリクエストが発生し、それらをすべて1つのエンティティとして扱うように割り当てられたデリゲートがある場合に、多くの分岐と醜いコードが実行を開始します。

どのようなデータが返されますか?これが含まれている場合はそれを行い、それ以外の場合はそれを行います。ビューにIDでタグを付けることができるように、これらの非同期リクエストにタグを付けることができると便利だと思います。

複数の非同期リクエストを処理するクラスを管理するのに最も効果的な方法は何でしょうか。

回答:


77

関連付けられたNSURLConnectionをキーとするCFMutableDictionaryRefで応答を追跡します。つまり:

connectionToInfoMapping =
    CFDictionaryCreateMutable(
        kCFAllocatorDefault,
        0,
        &kCFTypeDictionaryKeyCallBacks,
        &kCFTypeDictionaryValueCallBacks);

NSMutableDictionaryの代わりにこれを使用するのは奇妙に思えるかもしれませんが、このCFDictionaryはキー(NSURLConnection)のみを保持するのに対し、NSDictionaryはキーをコピーします(NSURLConnectionはコピーをサポートしていません)。

それが終わったら:

CFDictionaryAddValue(
    connectionToInfoMapping,
    connection,
    [NSMutableDictionary
        dictionaryWithObject:[NSMutableData data]
        forKey:@"receivedData"]);

これで、接続に関する情報を追跡するために使用できる各接続のデータの「情報」ディクショナリがあり、「情報」ディクショナリには、返信データを格納するために使用できる可変データオブジェクトがすでに含まれています。

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    NSMutableDictionary *connectionInfo =
        CFDictionaryGetValue(connectionToInfoMapping, connection);
    [[connectionInfo objectForKey:@"receivedData"] appendData:data];
}

一度に2つ以上の非同期接続がデリゲートメソッドに入る可能性があるため、正しい動作を保証するために何か特別なことはありますか?
PlagueHammer 2009

(私はここに新しい質問を作成しました:stackoverflow.com/questions/1192294/…
PlagueHammer

3
デリゲートが複数のスレッドから呼び出されている場合、これはスレッドセーフではありません。データ構造を保護するには、相互排他ロックを使用する必要があります。より良い解決策は、NSURLConnectionをサブクラス化し、応答とデータ参照をインスタンス変数として追加することです。私はノクターンの質問でこれを説明するより詳細な回答を提供しています:stackoverflow.com/questions/1192294/...
ジェームズ・ワルド

4
Aldi ... 同じスレッドからすべての接続を開始する場合は、スレッドセーフです(performSelector:onThread:withObject:waitUntilDone:を使用して接続開始メソッドを呼び出すことで簡単に実行できます)。すべての接続をNSOperationQueueに入れると、キューの最大同時操作数よりも多くの接続を開始しようとすると、異なる問題が発生します(操作は同時に実行されるのではなく、キューに入れられます)。NSOperationQueueはCPUにバインドされた操作に対しては適切に機能しますが、ネットワークにバインドされた操作に対しては、固定サイズのスレッドプールを使用しないアプローチを使用する方がよいでしょう。
Matt Gallagher、

1
iOS 6.0以降でそれを共有したかっただけで、の[NSMapTable weakToStrongObjectsMapTable]代わりにを使用しCFMutableDictionaryRefて面倒を節約できます。私にとってはうまくいきました。
シェイアビブ2014

19

2つの異なるNSURLConnectionsがあり、同じデリゲートを使用したいプロジェクトがあります。私が行ったのは、各接続に1つずつ、2つのプロパティをクラスに作成することでした。次に、デリゲートメソッドで、どの接続であるかを確認します


- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    if (connection == self.savingConnection) {
        [self.savingReturnedData appendData:data];
    }
    else {
        [self.sharingReturnedData appendData:data];
    }
}

これにより、必要に応じて特定の接続を名前でキャンセルすることもできます。


競合状態になるため、これには問題があることに注意してください。
追加

そもそも、各接続に名前(savingConnectionとsharingReturnedData)をどのように割り当てますか?
jsherk

@adit、いいえ、このコードに固有の競合状態はありません。競合状態を作成するには、接続作成コードをかなり使いこなす必要があります
Mike Abdullah

あなたの「解決策」は、上から引用して、元の質問が回避しようとしているものとまったく同じです:「...多くの分岐と醜いコードが定式化し始めます...」
stefanB

1
@aditなぜこれが競合状態につながるのですか?それは私にとって新しい概念です。
guptron 2013

16

データを保持するためのNSURLConnectionのサブクラス化はクリーンで、他のいくつかの回答よりもコードが少なく、柔軟性が高く、参照管理について考える必要が少なくなります。

// DataURLConnection.h
#import <Foundation/Foundation.h>
@interface DataURLConnection : NSURLConnection
@property(nonatomic, strong) NSMutableData *data;
@end

// DataURLConnection.m
#import "DataURLConnection.h"
@implementation DataURLConnection
@synthesize data;
@end

NSURLConnectionと同じように使用し、dataプロパティにデータを蓄積します。

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    ((DataURLConnection *)connection).data = [[NSMutableData alloc] init];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [((DataURLConnection *)connection).data appendData:data];
}

それでおしまい。

さらに進めたい場合は、数行だけのコードでコールバックとして機能するブロックを追加できます。

// Add to DataURLConnection.h/.m
@property(nonatomic, copy) void (^onComplete)();

次のように設定します。

DataURLConnection *con = [[DataURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
con.onComplete = ^{
    [self myMethod:con];
};
[con start];

そして、このようにロードが完了したときにそれを呼び出します:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    ((DataURLConnection *)connection).onComplete();
}

ブロックを拡張してパラメーターを受け入れるか、次のように引数なしのブロック内でそれを必要とするメソッドに引数としてDataURLConnectionを渡すだけです。


これは私の場合に非常にうまくいった素晴らしい答えです。とてもシンプルできれい!
jwarrent 2013年

8

これは新しい答えではありません。私にどうやって見せたか教えてください

同じクラスのデリゲートメソッド内で異なるNSURLConnectionを区別するために、NSMutableDictionaryを使用して、NSURLConnection (NSString *)descriptionをキーとして使用して設定および削除します。

私が選択したオブジェクトsetObject:forKeyは、使用の開始NSURLRequestに使用される一意のURLですNSURLConnection

NSURLConnectionが設定されると、

-(void)connectionDidFinishLoading:(NSURLConnection *)connection, it can be removed from the dictionary.

// This variable must be able to be referenced from - (void)connectionDidFinishLoading:(NSURLConnection *)connection
NSMutableDictionary *connDictGET = [[NSMutableDictionary alloc] init];
//...//

// You can use any object that can be referenced from - (void)connectionDidFinishLoading:(NSURLConnection *)connection
[connDictGET setObject:anyObjectThatCanBeReferencedFrom forKey:[aConnectionInstanceJustInitiated description]];
//...//

// At the delegate method, evaluate if the passed connection is the specific one which needs to be handled differently
if ([[connDictGET objectForKey:[connection description]] isEqual:anyObjectThatCanBeReferencedFrom]) {
// Do specific work for connection //

}
//...//

// When the connection is no longer needed, use (NSString *)description as key to remove object
[connDictGET removeObjectForKey:[connection description]];

5

私が取った1つのアプローチは、各接続のデリゲートと同じオブジェクトを使用しないことです。代わりに、起動される接続ごとに解析クラスの新しいインスタンスを作成し、デリゲートをそのインスタンスに設定します。


1つの接続に関して、はるかに優れたカプセル化。
Kedar Paranjape


2

私は通常、辞書の配列を作成します。各ディクショナリには、少しの識別情報、応答を格納するNSMutableDataオブジェクト、および接続自体があります。接続デリゲートメソッドが起動すると、接続のディクショナリを検索し、それに応じて処理します。


ベン、サンプルコードを1つ要求してもいいですか。私はあなたがそれをどのようにやっているかを想像しようとしていますが、それだけではありません。
Coocoo4Cocoa 2008

特にベン、どうやって辞書を調べますか?NSURLConnectionはNSCopyingを実装していないため、ディクショナリのディクショナリを持つことはできません(そのため、キーとして使用できません)。
Adam Ernst、

マットはCFMutableDictionaryを使用して以下の優れたソリューションを持っていますが、私は辞書の配列を使用しています。ルックアップには反復が必要です。最も効率的ではありませんが、十分に高速です。
ベンゴットリーブ

2

1つのオプションは、NSURLConnectionを自分でサブクラス化し、-tagまたは同様のメソッドを追加することです。NSURLConnectionの設計は意図的に非常に重要な部分であるため、これは完全に許容可能です。

または、接続のデータの作成と収集を担当するMyURLConnectionControllerクラスを作成することもできます。ロードが完了すると、メインコントローラオブジェクトに通知するだけで済みます。


2

iOS5以降では、クラスメソッドを使用できます sendAsynchronousRequest:queue:completionHandler:

完了ハンドラで応答が返されるため、接続を追跡する必要はありません。


1

私はASIHTTPRequestが好きです


私はASIHTTPRequestの「ブロック」実装が本当に好きです-それはちょうどJavaの匿名内部型のようです。これは、コードの清浄度と編成の点で他のすべてのソリューションに勝るものです。
Matt Lyons

1

他の回答で指摘されているように、connectionInfoをどこかに保存し、接続によって検索する必要があります。

これの最も自然なデータ型はですがNSMutableDictionary、受け入れることはできませんNSURLConnection接続はコピー、キーとして。

NSURLConnectionsキーとして使用する別のオプションは、次を使用することNSMutableDictionaryですNSValue valueWithNonretainedObject]

NSMutableDictionary* dict = [NSMutableDictionary dictionary];
NSValue *key = [NSValue valueWithNonretainedObject:aConnection]
/* store: */
[dict setObject:connInfo forKey:key];
/* lookup: */
[dict objectForKey:key];

0

NSURLConnectionをサブクラス化して、タグ、デリゲート、およびNSMutabaleDataを追加することにしました。リクエストを含むすべてのデータ管理を処理するDataControllerクラスがあります。DataControllerDelegateプロトコルを作成しました。これにより、個々のビュー/オブジェクトがDataControllerをリッスンして、リクエストがいつ完了したか、必要に応じてダウンロードされた量やエラーを確認できます。DataControllerクラスはNSURLConnectionサブクラスを使用して新しいリクエストを開始し、DataControllerをリッスンしてリクエストがいつ終了したかを知るデリゲートを保存できます。これは、XCode 4.5.2およびiOS 6での私の実用的なソリューションです。

DataControllerDelegateプロトコルを宣言するDataController.hファイル)。DataControllerもシングルトンです。

@interface DataController : NSObject

@property (strong, nonatomic)NSManagedObjectContext *context;
@property (strong, nonatomic)NSString *accessToken;

+(DataController *)sharedDataController;

-(void)generateAccessTokenWith:(NSString *)email password:(NSString *)password delegate:(id)delegate;

@end

@protocol DataControllerDelegate <NSObject>

-(void)dataFailedtoLoadWithMessage:(NSString *)message;
-(void)dataFinishedLoading;

@end

DataController.mファイルの主要なメソッド:

-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    NSURLConnectionWithDelegate *customConnection = (NSURLConnectionWithDelegate *)connection;
    NSLog(@"DidReceiveResponse from %@", customConnection.tag);
    [[customConnection receivedData] setLength:0];
}

-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    NSURLConnectionWithDelegate *customConnection = (NSURLConnectionWithDelegate *)connection;
    NSLog(@"DidReceiveData from %@", customConnection.tag);
    [customConnection.receivedData appendData:data];

}

-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
    NSURLConnectionWithDelegate *customConnection = (NSURLConnectionWithDelegate *)connection;
    NSLog(@"connectionDidFinishLoading from %@", customConnection.tag);
    NSLog(@"Data: %@", customConnection.receivedData);
    [customConnection.dataDelegate dataFinishedLoading];
}

-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    NSURLConnectionWithDelegate *customConnection = (NSURLConnectionWithDelegate *)connection;
    NSLog(@"DidFailWithError with %@", customConnection.tag);
    NSLog(@"Error: %@", [error localizedDescription]);
    [customConnection.dataDelegate dataFailedtoLoadWithMessage:[error localizedDescription]];
}

そしてリクエストを開始するには: [[NSURLConnectionWithDelegate alloc] initWithRequest:request delegate:self startImmediately:YES tag:@"Login" dataDelegate:delegate];

NSURLConnectionWithDelegate.h:@protocol DataControllerDelegate;

@interface NSURLConnectionWithDelegate : NSURLConnection

@property (strong, nonatomic) NSString *tag;
@property id <DataControllerDelegate> dataDelegate;
@property (strong, nonatomic) NSMutableData *receivedData;

-(id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately tag:(NSString *)tag dataDelegate:(id)dataDelegate;

@end

そしてNSURLConnectionWithDelegate.m:

#import "NSURLConnectionWithDelegate.h"

@implementation NSURLConnectionWithDelegate

-(id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately tag:(NSString *)tag dataDelegate:(id)dataDelegate {
    self = [super initWithRequest:request delegate:delegate startImmediately:startImmediately];
    if (self) {
        self.tag = tag;
        self.dataDelegate = dataDelegate;
        self.receivedData = [[NSMutableData alloc] init];
    }
    return self;
}

@end

0

すべてのNSURLConnectionにはハッシュ属性があり、この属性によってすべてを区別できます。

たとえば、接続の前後に特定の情報を保持する必要があるため、RequestManagerはこれを行うためのNSMutableDictionaryを持っています。

例:

// Make Request
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLConnection *c = [[NSURLConnection alloc] initWithRequest:request delegate:self];

// Append Stuffs 
NSMutableDictionary *myStuff = [[NSMutableDictionary alloc] init];
[myStuff setObject:@"obj" forKey:@"key"];
NSNumber *connectionKey = [NSNumber numberWithInt:c.hash];

[connectionDatas setObject:myStuff forKey:connectionKey];

[c start];

リクエスト後:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSLog(@"Received %d bytes of data",[responseData length]);

    NSNumber *connectionKey = [NSNumber numberWithInt:connection.hash];

    NSMutableDictionary *myStuff = [[connectionDatas objectForKey:connectionKey]mutableCopy];
    [connectionDatas removeObjectForKey:connectionKey];
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.