NSURLConnection и Grand Central Dispatch

Желательно ли заключить NSUrlConnection в блоки стиля gcd и запустить его в очереди low_priority?

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

Если я пойду по маршруту gcd, я не уверен, в каком потоке вызываются методы NSUrlConnectionDelegate.

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

- (void)connection:(NSURLConnection *)aConnection didReceiveResponse:(NSURLResponse *)response;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)incrementalData;
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;

Должен ли я просто вызывать синхронные версии, но завернутые в блоки gcd? А если я хочу отменить звонок, используйте dispatch_suspend?

dispatch_async(queue,^{
      NSString* result = [self mySynchronousHttp:someURLToInvoke];
      });

// If I need to cancel
dispatch_suspend(queue);

person Jonas Anderson    schedule 18.02.2011    source источник


Ответы (3)


Я рекомендую вам ознакомиться с сеансами WWDC о сетевых приложениях в iPhone OS.

  • WWDC 2010 Сессия 207 - Сетевые приложения для iPhone OS, часть 1
  • WWDC 2010 Сессия 208 - Сетевые приложения для iPhone OS, часть 2

Лектор сказал

"Threads Are Evil ™"

для сетевого программирования и рекомендуется использовать асинхронное сетевое программирование с RunLoop. Используйте фоновый поток (Grand Central Dispatch Concurrent Queue) для потоковой обработки данных, а не для сетевого программирования.

Кстати, сеансы Blocks и Grand Central Dispatch тоже достойны внимания.

  • WWDC 2010 Сессия 206 - Представляем блоки и Grand Central Dispatch на iPhone
  • WWDC 2010 Сессия 211 - Упрощение разработки приложений для iPhone с помощью Grand Central Dispatch

Я написал класс-оболочку для асинхронного NSURLConnection.


AsyncURLConnection.h

#import <Foundation/Foundation.h>

typedef void (^completeBlock_t)(NSData *data);
typedef void (^errorBlock_t)(NSError *error);

@interface AsyncURLConnection : NSObject
{
    NSMutableData *data_;
    completeBlock_t completeBlock_;
    errorBlock_t errorBlock_;
}

+ (id)request:(NSString *)requestUrl completeBlock:(completeBlock_t)completeBlock errorBlock:(errorBlock_t)errorBlock;
- (id)initWithRequest:(NSString *)requestUrl completeBlock:(completeBlock_t)completeBlock errorBlock:(errorBlock_t)errorBlock;
@end

AsyncURLConnection.m

#import "AsyncURLConnection.h"

@implementation AsyncURLConnection

+ (id)request:(NSString *)requestUrl completeBlock:(completeBlock_t)completeBlock errorBlock:(errorBlock_t)errorBlock
{
    return [[[self alloc] initWithRequest:requestUrl
        completeBlock:completeBlock errorBlock:errorBlock] autorelease];
}

- (id)initWithRequest:(NSString *)requestUrl completeBlock:(completeBlock_t)completeBlock errorBlock:(errorBlock_t)errorBlock
{

    if ((self=[super init])) {
        data_ = [[NSMutableData alloc] init];

        completeBlock_ = [completeBlock copy];
        errorBlock_ = [errorBlock copy];

        NSURL *url = [NSURL URLWithString:requestUrl];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        [NSURLConnection connectionWithRequest:request delegate:self];
    }

    return self;
}

- (void)dealloc
{
    [data_ release];

    [completeBlock_ release];
    [errorBlock_ release];
    [super dealloc];
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    [data_ setLength:0];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    [data_ appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    completeBlock_(data_);
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    errorBlock_(error);
}

@end

Как использовать класс AsyncURLConnection.

/*
 * AsyncURLConnection -request:completeBlock:errorBlock: have to be called
 * from Main Thread because it is required to use asynchronous API with RunLoop.
 */

[AsyncURLConnection request:url completeBlock:^(NSData *data) {

    /* success! */

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        /* process downloaded data in Concurrent Queue */

        dispatch_async(dispatch_get_main_queue(), ^{

            /* update UI on Main Thread */

        });
    });

} errorBlock:^(NSError *error) {

    /* error! */

}];
person Kazuki Sakamoto    schedule 01.03.2011
comment
это отличный пример кода, спасибо, что поделились. если бы я хотел добавить cancel метод к AsyncURLConnection, который отменял бы NSURLConnection внутри, как бы вы порекомендовали его реализовать? Добавить iVar для удержания соединения и просто позвонить по нему cancel? - person XJones; 16.04.2011
comment
Я изменил класс на "Отменить". ASyncURLConnection.h и AsyncURLConnection.m. Класс унаследован от NSURLConnection, поэтому вы можете вызвать метод отмены экземпляра класса. Но я еще не тестировал :-) - person Kazuki Sakamoto; 19.04.2011
comment
Лектор действительно говорит, что нити - это зло, но он не говорит, что GCD - это зло, он говорит, что это будущее. Во время лекции он сказал не использовать GCD, потому что он еще не входит в базовую структуру сетей, но теперь он: [NSURLConnection sendAsynchronousRequest:queue:completionHandler:] - person keegan3d; 18.11.2011
comment
Весь этот ответ кажется сейчас устаревшим - см. Комментарий от @ keegan3d - person whitneyland; 19.11.2011
comment
отличный код, я просто пытался понять ваш код, чтобы использовать его, но у меня есть одно сомнение, почему вы копируете блок, какова цель этого? - person Anshul; 11.02.2014
comment
Обычно блоки создаются в стеке, вам нужно вызвать Block_copy (или метод копирования Objective-C) для копирования объекта блока в кучу из стека, чтобы сохранить его. - person Kazuki Sakamoto; 12.02.2014

Создайте параллельную NSOperation, в которой вы запускаете асинхронный NSURLConnection.

person Nyx0uf    schedule 19.02.2011
comment
Другими словами, подход gcd не подходит для этого? Я думал, что gcd - это способ упростить такие вещи. - person Jonas Anderson; 23.02.2011

Взгляните на этот блок кода:

-(void)getDataFromServer
{
    NSDictionary *dicParams = [NSDictionary dictionaryWithObjectsAndKeys:
                           userId,    kUserID,
                           pageIndex,kPageIndex,
                           nil];

    NSString *yourURL = [self addQueryStringToUrlString:[NSString stringWithFormat:@"%@/%@",_PATH_,apiName] withDictionary:dicParams];


    NSString *queue_id = @"_queue_id_";
    dispatch_queue_t queue = dispatch_queue_create([queue_id UTF8String], 0);
    dispatch_queue_t main = dispatch_get_main_queue();

    dispatch_async(queue, ^{

        NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:yourURL] 
                                                    cachePolicy:NSURLRequestReloadIgnoringCacheData 
                                                timeoutInterval:60.0];
        [theRequest setHTTPMethod:@"GET"];
        [theRequest setValue:@"application/json" forHTTPHeaderField:@"Accept"];
        [theRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];

        NSError        *serviceError = nil;
        NSURLResponse  *serviceResponse = nil;
        NSData *dataResponse = [NSURLConnection sendSynchronousRequest:theRequest 
                                                     returningResponse:&serviceResponse 
                                                                 error:&serviceError];

        if(serviceError)
        {
            dispatch_sync(main, ^{

                // Update your UI

                if(serviceError.code == -1012){
                    // Log error
                }else{
                    // Log error
                }
            });
        }

        else
        {
            NSError *jsonError = nil;

            id dataObject = [NSJSONSerialization JSONObjectWithData:dataResponse 
                                                            options:kNilOptions 
                                                              error:&jsonError];
            NSMutableArray *arrResponse = (NSMutableArray *)dataObject;

            dispatch_sync(main, ^{

                // Update your UI
                [yourTableView reloadData];
            });
        }
    });
}

+(NSString*)addQueryStringToUrlString:(NSString *)urlString withDictionary:(NSDictionary *)dictionary
{
    NSMutableString *urlWithQuerystring = [[NSMutableString alloc] initWithString:urlString];

    for (id key in dictionary) {
        NSString *keyString = [key description];
        NSString *valueString = [[dictionary objectForKey:key] description];

        if ([urlWithQuerystring rangeOfString:@"?"].location == NSNotFound) {
            [urlWithQuerystring appendFormat:@"?%@=%@", [self urlEscapeString:keyString], [self urlEscapeString:valueString]];
        } else {
            [urlWithQuerystring appendFormat:@"&%@=%@", [self urlEscapeString:keyString], [self urlEscapeString:valueString]];
        }
    }
    return urlWithQuerystring;
}

+(NSString*)urlEscapeString:(NSString *)unencodedString
{
    CFStringRef originalStringRef = (__bridge_retained CFStringRef)unencodedString;
    NSString *s = (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,originalStringRef, NULL, NULL,kCFStringEncodingUTF8);
    CFRelease(originalStringRef);
    return s;
}
person NSPratik    schedule 31.07.2015