Управление несколькими асинхронными соединениями NSURLConnection - PullRequest
88 голосов
/ 02 декабря 2008

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

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

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

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

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

Ответы [ 13 ]

77 голосов
/ 02 декабря 2008

Я отслеживаю ответы в CFMutableDictionaryRef, ключом которого является NSURLConnection, связанный с ним. i.e.:

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];
}
19 голосов
/ 24 января 2011

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


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

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

16 голосов
/ 17 декабря 2012

Подклассы 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 и накапливайте данные в его свойстве 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];
}

Вот и все.

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

// 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, как показано

8 голосов
/ 07 июля 2010

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

Чтобы различать различные 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]];
5 голосов
/ 02 декабря 2008

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

4 голосов
/ 02 декабря 2008

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

2 голосов
/ 13 декабря 2012

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

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

2 голосов
/ 02 декабря 2008

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

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

2 голосов
/ 02 декабря 2008

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

1 голос
/ 26 мая 2010

Мне нравится ASIHTTPRequest .

...