Objective-C:不使用委托方法的异步/后台POST?

时间:2012-02-23 00:41:40

标签: objective-c ios cocoa-touch nsurlconnection

我需要对我的服务器进行一些POST调用,但我不需要阻止主线程。据我了解,NSMutableURLRequestNSURLConnection不是线程安全的,因此最好使用NSURLConnection的异步方法。

我的问题是,我如何将其很好地打包到方法中,而不必使用委托方法?我更愿意这样做:

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

这是使用以下方法轻松完成的方法:

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

将一切保存在一个方法中,然后只是从中返回我的数据真是太好了!

这有什么基于块的方法吗?当我需要为大约50个不同的调用编写方法并且每个调用需要使用相同的委托方法时,它就成了一个问题。我是以错误的方式解决这个问题吗?

这只需要适用于iOS5。

4 个答案:

答案 0 :(得分:9)

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和HTTP响应代码(因为服务器404响应可能与您的连接失败的观点一样多),显然。

答案 1 :(得分:1)

iOS 5.0 >您可以使用sendAsynchronousRequest方法查看NSURLConnection Class并使用块。如果你也想支持iOS 4.0 >那么你必须编写一个你自己的基于块的异步URL加载,这很容易编写。最好使用MKNetworkKit

但我不需要阻止主线程。据我所知,NSMutableURLRequest和NSURLConnection不是线程安全的,所以最好使用NSURLConnection的async方法。

你不想做同步网络连接,无论它被调用哪个都会阻塞线程(如果是它的主线程则更糟糕)。您可以在主线程上进行异步网络连接。如果你想在非主线程上调用NSURLConnection,那么必须在该线程上创建一个RunLoop(如果你不这样做,那么NSURLConnection的委托方法永远不会被调用)。

答案 2 :(得分:1)

我在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 *);

...您可以添加typedef以使这些块签名更漂亮......然后:

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

答案 3 :(得分:0)

我使用facade方法对发出同步调用的内部worker进行操作。根据您发送呼叫的速率,它可能会起作用。例如:

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

它可以很好地用于我的目的,但你可能需要深入研究异步的东西,这真的不是太糟糕。