Managing multiple asynchronous NSURLConnection con

2020-01-23 03:51发布

I have a ton of repeating code in my class that looks like the following:

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

The problem with asynchronous requests is when you have various requests going off, and you have a delegate assigned to treat them all as one entity, a lot of branching and ugly code begins to formulate going:

What kind of data are we getting back? If it contains this, do that, else do other. It would be useful I think to be able to tag these asynchronous requests, kind of like you're able to tag views with IDs.

I was curious what strategy is most efficient for managing a class that handles multiple asynchronous requests.

13条回答
够拽才男人
2楼-- · 2020-01-23 04:14

I track responses in an CFMutableDictionaryRef keyed by the NSURLConnection associated with it. i.e.:

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

It may seem odd to use this instead of NSMutableDictionary but I do it because this CFDictionary only retains its keys (the NSURLConnection) whereas NSDictionary copies its keys (and NSURLConnection doesn't support copying).

Once that's done:

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

and now I have an "info" dictionary of data for each connection that I can use to track information about the connection and the "info" dictionary already contains a mutable data object that I can use to store the reply data as it comes in.

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    NSMutableDictionary *connectionInfo =
        CFDictionaryGetValue(connectionToInfoMapping, connection);
    [[connectionInfo objectForKey:@"receivedData"] appendData:data];
}
查看更多
淡お忘
3楼-- · 2020-01-23 04:14

As pointed out by other answers, you should store connectionInfo somewhere and look up them by connection.

The most natural datatype for this is NSMutableDictionary, but it cannot accept NSURLConnection as keys as connections are non copyable.

Another option for using NSURLConnections as keys in NSMutableDictionary is using NSValue valueWithNonretainedObject]:

NSMutableDictionary* dict = [NSMutableDictionary dictionary];
NSValue *key = [NSValue valueWithNonretainedObject:aConnection]
/* store: */
[dict setObject:connInfo forKey:key];
/* lookup: */
[dict objectForKey:key];
查看更多
唯我独甜
4楼-- · 2020-01-23 04:16
等我变得足够好
5楼-- · 2020-01-23 04:17

I decided to subclass NSURLConnection and add a tag, delegate, and a NSMutabaleData. I have a DataController class that handles all of the data management, including the requests. I created a DataControllerDelegate protocol, so that individual views/objects can listen to the DataController to find out when their requests were finished, and if needed how much has been downloaded or errors. The DataController class can use the NSURLConnection subclass to start a new request, and save the delegate that wants to listen to the DataController to know when the request has finished. This is my working solution in XCode 4.5.2 and ios 6.

The DataController.h file that declares the DataControllerDelegate protocol). The DataController is also a singleton:

@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

The key methods in the DataController.m file:

-(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]];
}

And to start a request: [[NSURLConnectionWithDelegate alloc] initWithRequest:request delegate:self startImmediately:YES tag:@"Login" dataDelegate:delegate];

The 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

And the 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
查看更多
唯我独甜
6楼-- · 2020-01-23 04:22

One approach I've taken is to not use the same object as the delegate for each connection. Instead, I create a new instance of my parsing class for each connection that is fired off and set the delegate to that instance.

查看更多
对你真心纯属浪费
7楼-- · 2020-01-23 04:23

One option is just to subclass NSURLConnection yourself and add a -tag or similar method. The design of NSURLConnection is intentionally very bare bones so this is perfectly acceptable.

Or perhaps you could create a MyURLConnectionController class that is responsible for creating and collecting a connection's data. It would then only have to inform your main controller object once loading is finished.

查看更多
登录 后发表回答