Цель-C: Асинхронный / Фоновый POST без использования метода делегата? - PullRequest
7 голосов
/ 23 февраля 2012

Мне нужно сделать несколько POST-вызовов на мой сервер, но мне не нужно блокировать основной поток.Как я понимаю, NSMutableURLRequest и NSURLConnection не являются потокобезопасными, поэтому лучше всего использовать асинхронный метод NSURLConnection.

. Мой вопрос по этому поводу состоит в том, как я могу красиво упаковать егометод, вместо того, чтобы использовать метод делегата?Я бы предпочел сделать:

NSData *returnedData = [Utility postDataToURL:@"some string of data"];

Вот как это легко сделать с помощью следующего метода:

[NSURLConnection sendSynchronousRequest:serviceRequest returningResponse:&serviceResponse error:&serviceError];

Так приятно держать все в одном методе, а потом простоданные возвращаются из него!

Есть ли для этого какие-либо блочные методы?Это становится проблемой, когда мне нужно написать методы для примерно 50 различных вызовов, и каждый из них должен использовать один и тот же метод делегата.Я поступаю неправильно?

Это нужно будет только для iOS5.

Ответы [ 4 ]

9 голосов
/ 23 февраля 2012

iOS 5 добавляет sendAsynchronousRequest:queue:completionHandler:, что делает то, что я думаю, что вы хотите. Я настроил свой код, чтобы использовать его, если он доступен, но для выполнения синхронной выборки в фоновой очереди GCD и перехода к основному потоку с результатом, если это не так. Последний будет менее энергоэффективным, но он просто будет поддерживать устаревшую поддержку.

if([NSURLConnection respondsToSelector:@selector(sendAsynchronousRequest:queue:completionHandler:)])
{
    // we can use the iOS 5 path, so issue the asynchronous request
    // and then just do whatever we want to do
    [NSURLConnection sendAsynchronousRequest:request
        queue:[NSOperationQueue mainQueue]
        completionHandler:
        ^(NSURLResponse *response, NSData *data, NSError *error)
        {
            [self didLoadData:data];
        }];
}
else
{
    // fine, we'll have to do a power inefficient iOS 4 implementation;
    // hop onto the global dispatch queue...
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
    ^{
        // ... perform a blocking, synchronous URL retrieval ...
        NSError *error = nil;
        NSURLResponse *urlResponse = nil;
        NSData *responseData =
            [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&error];

        // ... and hop back onto the main queue to handle the result
        dispatch_async(dispatch_get_main_queue(),
        ^{
            [self didLoadData:responseData];
        });
    });
}

В рабочем коде вы на самом деле проверяли бы коды ответов error s и HTTP (поскольку ответ сервера 404, вероятно, является такой же ошибкой с вашей точки зрения, как и ошибка соединения), очевидно.

1 голос
/ 23 февраля 2012

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

BetterNSURLConnection.h

@property (retain, nonatomic) NSURLRequest *request;

BetterNSURLConnection.m

@property (retain, nonatomic) NSURLConnection *connection;
@property (retain, nonatomic) NSHTTPURLResponse *response;
@property (retain, nonatomic) NSMutableData *responseData;

@property (copy, nonatomic) void (^completionBlock)(id, NSHTTPURLResponse *);
@property (copy, nonatomic) void (^errorBlock)(NSError *);

... Вы можете добавить typedefs, чтобы сделать эти подписи блоков красивее ... затем:

@synthesize connection = _connection;
@synthesize response = _response;
@synthesize responseData = _responseData;
@synthesize completionBlock = _completionBlock;
@synthesize errorBlock = _errorBlock;
@synthesize request=_request;


- (void)startWithCompletion:(void (^)(id, NSHTTPURLResponse *))completionBlock error:(void (^)(NSError *))errorBlock {

    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

    self.completionBlock = completionBlock;
    self.errorBlock = errorBlock;
    self.responseData = [NSMutableData data];

    NSURLConnection *connection = [NSURLConnection connectionWithRequest:self.request delegate:self];
    self.connection = connection;
    [self.connection start];
    [connection release];
}

... тогда сделайте делегата следующим образом:

- (void)connection:(NSURLConnection *)aConnection didReceiveResponse:(NSHTTPURLResponse *)response {

    [self.responseData setLength:0];
    self.response = response;
}

- (void)connection:(NSURLConnection *)aConnection didReceiveData:(NSData *)data {

    [self.responseData appendData:data];
}

- (void)connection:(NSURLConnection *)aConnection didFailWithError:(NSError *)error {

    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    self.errorBlock(error);
    self.connection = nil;
}

- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {

    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;

    if (self.response.statusCode >= 400) {
        self.errorBlock(error);
    } else {
        // i do json requests, and call a json parser here, but you might want to do something different
        id result = [self parseResponse:self.responseData];
        self.completionBlock(result, self.response);
    }
    self.connection = nil;
}
1 голос
/ 23 февраля 2012

iOS 5.0 > вы можете использовать метод sendAsynchronousRequest, посмотрите на NSURLConnection Class и он использует блоки.Если вы хотите поддерживать iOS 4.0 >, то вам нужно написать одну из своих асинхронных загрузок URL на основе блоков, которую довольно просто написать.Вам лучше использовать MKNetworkKit .

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

Вы не хотите выполнять синхронное сетевое соединение, оно блокирует поток в зависимости от того, что он называетсяот (это еще хуже, если его основной поток).Вы можете сделать асинхронное сетевое соединение в основном потоке.Если вы хотите сделать вызов NSURLConnection в неосновном потоке, то вам нужно создать RunLoop в этом потоке (если вы этого не сделаете, то методы делегирования NSURLConnection никогда не будут вызваны).

0 голосов
/ 23 февраля 2012

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

// Presented in @interface
-(void)sendPostRequest {
   // Last chance to update main thread/UI
   NSInvocationOperation *op = [[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(sendPostRequest_internal) object:nil] autorelease];
   [opQueue addOperation:op];
}

// Hidden in @implementation
-(void)sendPostRequest_internal {
   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

   NSURLRequest *request = // yadda, you might use NSURLMutableRequest
   NSURLResponse *response = nil;
   NSError *error = nil;
   NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:response error:error];

   // process data, retain things as needed, post results using performSelectorOnMainThread:

   [pool release];
}

Это работает довольно хорошо для моих целей, но вам, возможно, придется углубиться в асинхронные вещи, что на самом деле неплохо.

...