2008-12-01 2 views
88

У меня есть тонны повторяющегося кода в моем классе, который выглядит следующим образом:Управление несколько асинхронного соединения NSURLConnection

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

Проблема с асинхронными запросами, когда у вас есть различные запросы уходят, и у вас есть делегат назначенный рассматривать их как единое целое, много разветвляющихся и уродливых код начинает формулировать:

Какие данные мы возвращаемся? Если он содержит это, сделайте это, иначе сделайте другое. Было бы полезно, я думаю, иметь возможность отмечать эти асинхронные запросы, вроде как вы можете пометить представления с идентификаторами.

Мне было любопытно, какая стратегия наиболее эффективна для управления классом, который обрабатывает несколько асинхронных запросов.

ответ

77

Я отслеживаю ответы в 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]; 
} 
+0

Поскольку возможно, что два или более асинхронных соединения могут одновременно вводить методы делегата, есть ли что-то конкретное, что нужно сделать для обеспечения правильного поведения? – PlagueHammer 2009-07-28 06:48:40

+0

(Здесь я задаю новый вопрос: http://stackoverflow.com/questions/1192294/cocoa-any-checks-required-for-multiple-asynchronous-nsurlconnections) – PlagueHammer 2009-07-28 07:02:28

+3

Это не потокобезопасно, если делегат вызывается из нескольких потоков. Вы должны использовать блокировки взаимного исключения для защиты структур данных. Лучшим решением является подклассификация NSURLConnection и добавление ответов и данных в качестве переменных экземпляра. Я предоставляю более подробный ответ, объясняющий это по вопросу Ноктюрна: http://stackoverflow.com/questions/1192294/cocoa-any-checks-required-for-multiple-asynchronous-nsurlconnections – 2009-12-21 05:11:24

2

Обычно я создаю массив словарей. В каждом словаре есть немного идентификационной информации, объекта NSMutableData для хранения ответа и самого соединения. Когда запускается метод делегирования соединения, я просматриваю словарь соединения и обрабатываю его соответствующим образом.

+0

Бен, было бы хорошо, чтобы попросить вас кусок образец кода? Я пытаюсь представить себе, как вы это делаете, но это еще не все. – Coocoo4Cocoa 2008-12-01 21:41:22

+0

В частности, Бен, как искать словарь? У вас не может быть словаря словарей, поскольку NSURLConnection не реализует NSCopying (поэтому его нельзя использовать в качестве ключа). – 2008-12-01 22:19:18

+0

Мэтт имеет превосходное решение ниже, используя CFMutableDictionary, но я использую массив словарей. Для поиска требуется итерация. Он не самый эффективный, но он достаточно быстр. – 2008-12-02 00:10:20

2

Одним из вариантов является просто подкласс NSURLConnection себя и добавить -tag или подобный метод. Дизайн NSURLConnection преднамеренно очень голые кости, поэтому это вполне приемлемо.

Возможно, вы можете создать класс MyURLConnectionController, который отвечает за создание и сбор данных соединения. После этого загрузка должна была бы только сообщить ваш основной объект контроллера.

5

Один из моих подходов - не использовать тот же объект, что и делегат для каждого соединения. Вместо этого я создаю новый экземпляр моего класса синтаксического анализа для каждого подключенного увольнения и назначает делегат этому экземпляру.

4

Попробуйте мой собственный класс, MultipleDownload, который обрабатывает все это для вас.

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

ЭТО НЕ НОВЫЙ ОТВЕТ. ПОЖАЛУЙСТА, ПОЗВОЛЯЙ, МНЕ ПОКАЗЫВАЙТЕ, ЧТО Я ДЕЛАТЬ

Чтобы различать различные NSURLConnection в рамках методов делегата одного класса, я использую NSMutableDictionary для установки и удаления NSURLConnection, используя его (NSString *)description как ключ.

Объект, который я выбрал для 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]]; 
19

У меня есть проект, где у меня есть два различных NSURLConnections, и хотел использовать один и тот же делегат. Я создал два свойства в моем классе, по одному для каждого соединения. Затем в методе делегата, я проверяю, если какое соединение он


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

Это также позволяет мне отменить конкретное соединение по имени, когда это необходимо.

0

Я решил подклассифицировать 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 
2

в iOS5 и выше, вы можете просто использовать метод класса sendAsynchronousRequest:queue:completionHandler:

Нет необходимости следить за соединениями, так как возвращается реагирования в завершение обработчик.

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 и накапливать данные в свойстве данных:

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

Вы можете расширить блок для принятия параметров или просто p оставьте DataURLConnection в качестве аргумента для метода, который нуждается в нем в блоке no-args, как показано

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