web-dev-qa-db-fra.com

Gestion de plusieurs connexions NSURLConnection asynchrones

J'ai une tonne de code répétitif dans ma classe qui ressemble à ceci:

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

Le problème avec les demandes asynchrones est lorsque vous avez plusieurs demandes en cours et qu'un délégué est affecté à toutes les traiter comme une seule entité, beaucoup de code de branchement et laid commence à se formuler:

Quel type de données récupérons-nous? S'il contient ceci, faites-le, sinon faites-en d'autres. Il serait utile, je pense, de pouvoir marquer ces demandes asynchrones, un peu comme si vous pouviez marquer des vues avec des ID.

J'étais curieux de savoir quelle stratégie est la plus efficace pour gérer une classe qui gère plusieurs demandes asynchrones.

88
Coocoo4Cocoa

Je fais le suivi des réponses dans un CFMutableDictionaryRef saisi par le NSURLConnection qui lui est associé. c'est à dire.:

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

Cela peut sembler étrange d'utiliser ceci au lieu de NSMutableDictionary mais je le fais parce que ce CFDictionary ne conserve que ses clés (NSURLConnection) tandis que NSDictionary copie ses clés (et NSURLConnection ne prend pas en charge la copie).

Une fois cela fait:

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

et maintenant j'ai un dictionnaire de données "info" pour chaque connexion que je peux utiliser pour suivre les informations sur la connexion et le dictionnaire "info" contient déjà un objet de données modifiable que je peux utiliser pour stocker les données de réponse dès leur arrivée.

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

J'ai un projet où j'ai deux connexions NSURLConnections distinctes et je voulais utiliser le même délégué. J'ai créé deux propriétés dans ma classe, une pour chaque connexion. Ensuite, dans la méthode déléguée, je vérifie si de quelle connexion il s'agit


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

Cela me permet également d'annuler une connexion spécifique par nom en cas de besoin.

19
jbarnhart

Le sous-classement NSURLConnection pour conserver les données est propre, moins de code que certaines des autres réponses, est plus flexible et nécessite moins de réflexion sur la gestion des références.

// 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

Utilisez-le comme vous le feriez pour NSURLConnection et accumulez les données dans sa propriété 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];
}

C'est ça.

Si vous voulez aller plus loin, vous pouvez ajouter un bloc pour servir de rappel avec juste quelques lignes de code supplémentaires:

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

Réglez-le comme ceci:

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

et l'invoquer lorsque le chargement est terminé comme ceci:

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

Vous pouvez étendre le bloc pour accepter des paramètres ou simplement passer le DataURLConnection comme argument à la méthode qui en a besoin dans le bloc sans argument comme indiqué

16
Pat Niemeyer

CECI IS PAS UNE NOUVELLE RÉPONSE. S'IL VOUS PLAÎT LAISSEZ-MOI VOUS MONTRER COMMENT J'AI FAIT

Pour distinguer différents NSURLConnection dans les méthodes déléguées de la même classe, j'utilise NSMutableDictionary, pour définir et supprimer NSURLConnection, en utilisant son (NSString *)description comme clé.

L'objet que j'ai choisi pour setObject:forKey est l'URL unique utilisée pour lancer NSURLRequest, que NSURLConnection utilise.

Une fois défini, NSURLConnection est évalué à

-(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]];
8
petershine

Une approche que j'ai adoptée consiste à ne pas utiliser le même objet que le délégué pour chaque connexion. Au lieu de cela, je crée une nouvelle instance de ma classe d'analyse pour chaque connexion qui est déclenchée et définit le délégué sur cette instance.

5
Brad The App Guy

Essayez ma classe personnalisée, MultipleDownload , qui gère tout cela pour vous.

4
leonho

dans iOS5 et au-dessus, vous pouvez simplement utiliser la méthode de classe sendAsynchronousRequest:queue:completionHandler:

Pas besoin de garder une trace des connexions puisque la réponse revient dans le gestionnaire d'achèvement.

2
Yariv Nissim

Je crée généralement un tableau de dictionnaires. Chaque dictionnaire a un peu d'informations d'identification, un objet NSMutableData pour stocker la réponse et la connexion elle-même. Lorsqu'une méthode de délégué de connexion se déclenche, je recherche le dictionnaire de la connexion et le gère en conséquence.

2
Ben Gottlieb

Une option consiste simplement à sous-classer NSURLConnection vous-même et à ajouter un -tag ou une méthode similaire. La conception de NSURLConnection est intentionnellement très nue, ce qui est parfaitement acceptable.

Ou peut-être pourriez-vous créer une classe MyURLConnectionController chargée de créer et de collecter les données d'une connexion. Il n'aurait alors qu'à informer votre objet contrôleur principal une fois le chargement terminé.

2
Mike Abdullah

Comme indiqué par d'autres réponses, vous devez stocker connectionInfo quelque part et les rechercher par connexion.

Le type de données le plus naturel pour cela est NSMutableDictionary, mais il ne peut pas accepter NSURLConnection comme clés car les connexions ne sont pas copiables.

Une autre option pour utiliser NSURLConnections comme clés dans NSMutableDictionary utilise NSValue valueWithNonretainedObject]:

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

J'ai décidé de sous-classer NSURLConnection et d'ajouter une balise, un délégué et un NSMutabaleData. J'ai une classe DataController qui gère toute la gestion des données, y compris les demandes. J'ai créé un protocole DataControllerDelegate, afin que les vues/objets individuels puissent écouter le DataController pour savoir quand leurs demandes ont été terminées et, si nécessaire, combien ont été téléchargés ou erreurs. La classe DataController peut utiliser la sous-classe NSURLConnection pour démarrer une nouvelle demande et enregistrer le délégué qui souhaite écouter le DataController pour savoir quand la demande est terminée. Ceci est ma solution de travail dans XCode 4.5.2 et ios 6.

Fichier DataController.h qui déclare le protocole DataControllerDelegate). Le DataController est également un 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

Les méthodes clés du fichier 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]];
}

Et pour lancer une demande: [[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

Et le 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
Chris Slade

Chaque NSURLConnection a un attribut de hachage, vous pouvez tout discriminer par cet attribut.

Par exemple, j'ai besoin de conserver certaines informations avant et après la connexion, donc mon RequestManager a un NSMutableDictionary pour le faire.

Un exemple:

// 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];

Après demande:

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