Управление несколькими асинхронными соединениями NSURLConnection
У меня есть тонна повторяющегося кода в моем классе, который выглядит следующим образом:
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self];
Проблема с асинхронными запросами заключается в том, когда у вас разные запросы, и у вас есть делегат, назначенный для обработки их всех как одного объекта, много разветвляющегося и уродливого кода начинает формулировать:
Какие данные мы возвращаем? Если он содержит это, сделайте это, иначе сделайте другое. Было бы полезно, я думаю, иметь возможность отмечать эти асинхронные запросы, вроде как вы можете пометить представления с идентификаторами.
Мне было любопытно, какая стратегия наиболее эффективна для управления классом, который обрабатывает несколько асинхронных запросов.
Ответы
Ответ 1
Я отслеживаю ответы в CFMutableDictionaryRef, связанный с NSURLConnection, связанным с ним. то есть:.
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
У меня есть проект, в котором у меня есть два разных NSURLConnections, и я хотел использовать один и тот же делегат. Я создал два свойства в моем классе, по одному для каждого соединения. Затем в методе делегата я проверяю, установлено ли какое соединение
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
if (connection == self.savingConnection) {
[self.savingReturnedData appendData:data];
}
else {
[self.sharingReturnedData appendData:data];
}
}
Это также позволяет мне отменить определенное соединение по имени, когда это необходимо.
Ответ 3
Подкласс 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, и накапливайте данные в свойстве данных:
- (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 в качестве аргумента методу, который нуждается в нем в блоке no-args, как показано
Ответ 4
ЭТО НЕ НОВЫЙ ОТВЕТ. ПОЖАЛУЙСТА, ПОЗВОЛЬТЕ, ЧТО Я ПОКАЗАЛ, ЧТО Я СКАЗАЛ
Чтобы отличить разные NSURLConnection в рамках одних и тех же методов делегатов класса, я использую NSMutableDictionary для установки и удаления NSURLConnection с помощью клавиши (NSString *)description
as.
Объект, который я выбрал для setObject:forKey
, - это уникальный URL-адрес, который используется для запуска NSURLRequest
, используется 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
Один из моих подходов - не использовать тот же объект, что и делегат для каждого соединения. Вместо этого я создаю новый экземпляр моего класса синтаксического анализа для каждого запущенного соединения и назначает делегат этому экземпляру.
Ответ 6
Попробуйте использовать свой собственный класс MultipleDownload, который обрабатывает все это для вас.
Ответ 7
Я обычно создаю массив словарей. В каждом словаре есть немного идентификационной информации, объекта NSMutableData для хранения ответа и самого соединения. Когда запускается метод делегирования соединения, я просматриваю словарь соединений и обрабатываю его соответствующим образом.
Ответ 8
Один из вариантов заключается только в подклассе NSURLConnection и добавлении метода -tag или аналогичного метода. Дизайн NSURLConnection преднамеренно очень голые кости, поэтому это вполне приемлемо.
Или, возможно, вы можете создать класс MyURLConnectionController, который отвечает за создание и сбор данных соединения. Затем он должен будет только сообщить ваш основной объект контроллера после завершения загрузки.
Ответ 9
в iOS5 и выше вы можете просто использовать метод класса
sendAsynchronousRequest:queue:completionHandler:
Не нужно отслеживать соединения, так как ответ возвращается в обработчике завершения.
Ответ 10
Как указывалось другими ответами, вы должны где-то хранить 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];
Ответ 11
Мне нравится ASIHTTPRequest.
Ответ 12
Я решил подклассировать NSURLConnection и добавить тег, делегат и NSMutabaleData. У меня есть класс DataController, который обрабатывает все управление данными, включая запросы. Я создал протокол DataControllerDelegate, так что отдельные представления/объекты могут прослушивать DataController, чтобы узнать, когда их запросы были завершены, и при необходимости, сколько было загружено или ошибок. Класс DataController может использовать подкласс NSURLConnection для запуска нового запроса и сохранять делегата, который хочет прослушать DataController, чтобы узнать, когда запрос завершен. Это мое рабочее решение в XCode 4.5.2 и ios 6.
Файл DataController.h, который объявляет протокол DataControllerDelegate). 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
Ответ 13
Каждый 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];
}