Zarządzanie wieloma asynchronicznymi połączeniami NSURLConnection

Mam mnóstwo powtarzających się Kodów w mojej klasie, które wyglądają następująco:

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

Problem z żądaniami asynchronicznymi polega na tym, że różne żądania są wyłączone i masz przypisanego delegata, który traktuje je wszystkie jako jedną jednostkę, wiele rozgałęzień i brzydkiego kodu zaczyna formułować:

Jakie dane odzyskujemy? Jeśli Zawiera to, zrób tamto, w przeciwnym razie zrób inne. Myślę, że byłoby to przydatne, aby móc oznaczyć te asynchroniczne żądania, tak jakbyś mógł oznaczyć widoki Idami.

Byłem ciekaw, jaka strategia jest najbardziej efektywna w zarządzaniu klasą, która obsługuje wiele asynchronicznych żądań.

Author: iDeveloper, 2008-12-01

13 answers

Śledzę odpowiedzi w CFMutableDictionaryRef keyed przez nsurlconnection z nim związane. tj.:

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

Może wydawać się dziwne używanie tego zamiast NSMutableDictionary, ale robię to, ponieważ ten CFDictionary zachowuje tylko swoje klucze (nsurlconnection), podczas gdy nsdictionary kopiuje swoje klucze (a NSURLConnection nie obsługuje kopiowania).

Kiedy to się skończy:

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

I teraz mam "info" słownik danych dla każdego połączenia, które mogę wykorzystać do śledzenia informacji o połączeniu i słownik "info" zawiera już zmienny obiekt danych, którego mogę użyć do przechowywania danych odpowiedzi, gdy się pojawi.

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    NSMutableDictionary *connectionInfo =
        CFDictionaryGetValue(connectionToInfoMapping, connection);
    [[connectionInfo objectForKey:@"receivedData"] appendData:data];
}
 77
Author: Matt Gallagher,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-09 00:03:14

Mam projekt, w którym mam dwa różne połączenia NSURLConnections i chciałem użyć tego samego delegata. To, co zrobiłem, to stworzenie dwóch właściwości w mojej klasie, po jednym dla każdego połączenia. Następnie w metodzie delegate sprawdzam, czy które to połączenie


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

Pozwala mi to również anulować określone połączenie po nazwie, gdy jest to potrzebne.

 19
Author: jbarnhart,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-24 20:18:22

Podklasowanie NSURLConnection do przechowywania danych jest czyste, mniej kodu niż niektóre inne odpowiedzi, jest bardziej elastyczne i wymaga mniej myślenia o zarządzaniu odniesieniami.

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

Użyj go tak, jak NSURLConnection i zgromadź dane w jego właściwości 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];
}
To wszystko.

Jeśli chcesz iść dalej, możesz dodać blok, który służy jako callback za pomocą kilku kolejnych linijek kodu:

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

Ustaw tak:

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

I wywołaj je, gdy Ładowanie kończy się tak:

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

Możesz rozszerzyć blok tak, aby akceptował parametry lub po prostu przekazać DataURLConnection jako argument do metody, która potrzebuje go w bloku no-args, jak pokazano

 16
Author: Pat Niemeyer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-17 22:22:24

TO NIE JEST NOWA ODPOWIEDŹ. PROSZĘ, POZWÓL MI POKAZAĆ CI, JAK TO ZROBIŁEM

Aby odróżnić różne nsurlconnection wewnątrz metod delegata tej samej klasy, używam NSMutableDictionary, aby ustawić i usunąć NSURLConnection, używając jego (NSString *)description jako klucza.

Obiekt, dla którego wybrałem setObject:forKey jest unikalnym adresem URL, który jest używany do inicjowania NSURLRequest, zastosowań NSURLConnection.

Po ustawieniu NSURLConnection jest oceniane na

-(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
Author: petershine,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-28 10:14:19

Jedno podejście, które zastosowałem, to nie używanie tego samego obiektu co delegat dla każdego połączenia. Zamiast tego tworzę nową instancję mojej klasy parsującej dla każdego połączenia, które jest wyłączone i ustawiam delegata na tę instancję.

 5
Author: Brad The App Guy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-02 05:35:18

Wypróbuj moją niestandardową klasę, MultipleDownload , która obsługuje to wszystko za Ciebie.

 4
Author: leonho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-02 08:51:47

Zwykle tworzę tablicę słowników. Każdy słownik ma trochę informacji identyfikujących, obiekt NSMutableData do przechowywania odpowiedzi i samo połączenie. Kiedy wywołana jest metoda delegata połączenia, sprawdzam słownik połączenia i odpowiednio go obsługuję.

 2
Author: Ben Gottlieb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-01 21:38:23

Jedną z opcji jest samodzielne podklasowanie NSURLConnection i dodanie-tagu lub podobnej metody. Konstrukcja NSURLConnection jest celowo bardzo naga, więc jest to całkowicie dopuszczalne.

A może mógłbyś stworzyć klasę MyURLConnectionController, która jest odpowiedzialna za tworzenie i zbieranie danych połączenia. Musiałby wtedy tylko poinformować Główny obiekt kontrolera po zakończeniu ładowania.

 2
Author: Mike Abdullah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-02 00:20:52

W iOS5 i wyżej możesz po prostu użyć metody class sendAsynchronousRequest:queue:completionHandler:

Nie ma potrzeby śledzenia połączeń, ponieważ odpowiedź powraca w funkcji obsługi zakończenia.

 2
Author: Yariv Nissim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-13 00:06:56
 1
Author: ruipacheco,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-26 11:24:46

Jak wskazują inne odpowiedzi, powinieneś gdzieś przechowywać connectionInfo i szukać ich po połączeniu.

Najbardziej naturalnym typem danych jest NSMutableDictionary, ale nie może przyjmować NSURLConnection jako kluczy, ponieważ połączenia nie są kopiowalne.

Inną opcją użycia NSURLConnections jako kluczy w NSMutableDictionary jest użycie NSValue valueWithNonretainedObject]:

NSMutableDictionary* dict = [NSMutableDictionary dictionary];
NSValue *key = [NSValue valueWithNonretainedObject:aConnection]
/* store: */
[dict setObject:connInfo forKey:key];
/* lookup: */
[dict objectForKey:key];
 1
Author: mfazekas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-14 22:04:01

Postanowiłem podklasować NSURLConnection i dodać tag, delegate i nsmutabaledata. Mam klasę DataController, która obsługuje wszystkie zarządzanie danymi, łącznie z żądaniami. Stworzyłem protokół DataControllerDelegate, aby poszczególne widoki / obiekty mogły nasłuchiwać DataController, aby dowiedzieć się, kiedy ich żądania zostały zakończone, a w razie potrzeby, ile zostało pobrane lub jakie są błędy. Klasa DataController może użyć podklasy Nsurlconnection, aby uruchomić nowe żądanie i zapisać delegat, który chce słuchać kontrolera DataController, aby wiedzieć, kiedy żądanie zostało zakończone. To jest moje rozwiązanie robocze w XCode 4.5.2 i ios 6.

Kontroler Danych.plik H deklarujący protokół DataControllerDelegate). DataController jest również singletonem:

@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

Kluczowe metody w Datacontrolle.plik 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]];
}

I aby rozpocząć żądanie: [[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

Oraz Nsurlconnection Withdelegate.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
Author: Chris Slade,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-14 00:44:54

Każda NSURLConnection ma atrybut hash, możesz rozróżnić wszystkie przez ten atrybut.

Na przykład muszę mantain pewnych informacji przed i po połączeniu, więc mój RequestManager ma NSMutableDictionary, aby to zrobić.

Przykład:

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

Po zapytaniu:

- (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
Author: eold,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-23 10:27:47