AFNetworking能否同步返回数据(在块内)?

47

我有一个使用AFJSONRequestOperation的函数,我希望在成功后只返回结果。你能给我指点一下方向吗?我对blocks和AFNetworking还不是很了解。

-(id)someFunction{
    __block id data;

    AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
        success:^(NSURLRequest *request, NSHTTPURLResponse *response, id json){
            data = json;
            return data; // won't work
        }
        failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error){

        }];



    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation: operation];

    return data; // will return nil since the block doesn't "lock" the app.
}
6个回答

57
为了阻塞主线程的执行直到操作完成,您可以在将操作添加到操作队列后使用[operation waitUntilFinished]。在这种情况下,您不需要在块中使用return;设置__block变量就足够了。
话虽如此,我强烈不建议强制将异步操作转换为同步方法。有时很难理解,但如果有任何方法可以将其结构化为异步,则几乎肯定是正确的方式。

嘿,马特,谢谢你的回复。通常我会异步使用我的数据,但是对于这个特定的情况,我必须从API返回一些数据,所以除非你能推荐一些行动方式,否则我不太清楚还有其他方法。 :) - Shai Mishali
我无法弄清如何设置超时时间。如果我进行POST请求怎么办?我必须等待超过两分钟吗?这让我无法切换到AFNetworking。关于超时问题,请参见此处:https://dev59.com/SGsy5IYBdhLWcg3w0RM5#8714479 - borisdiakur
7
很遗憾,waitUntilFinished技巧对我不起作用。 我有几个本质上是同步的业务方法。 AFNetworking完全忽略了这种情况,这非常遗憾。 抱歉,无法提供上下文或解释。 - nixau
9
我怀疑"waitUntilFinished"技巧对一些人无效,因为默认情况下成功和失败的块是在操作完成后使用"dispatch_async"在主队列上执行的。如果你没有在运行循环内执行,例如单元测试,则程序可能会提前退出而没有给GCD运行回调的机会。 - Tim Potter
4
我认为理想情况下,任何网络 SDK 都应该允许用户选择是否需要异步操作,而不是强制采用某种特定的模型,尽管它可以提出建议。 - Joseph Earl
显示剩余4条评论

12

我正在使用信号量来解决这个问题。这段代码是在我自己从AFHTTPClient继承的类中实现的。

__block id result = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSURLRequest *req = [self requestWithMethod:@"GET"
                                       path:@"someURL"
                                 parameters:nil];
AFHTTPRequestOperation *reqOp = [self HTTPRequestOperationWithRequest:req
                                                              success:^(AFHTTPRequestOperation *operation, id responseObject) {
                                                                  result = responseObject;                                                                          
                                                                  dispatch_semaphore_signal(semaphore);
                                                              }
                                                              failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                                                                  dispatch_semaphore_signal(semaphore);
                                                              }];
reqOp.failureCallbackQueue = queue;
reqOp.successCallbackQueue = queue;
[self enqueueHTTPRequestOperation:reqOp];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
return result;

11

我建议您不要使用AFNetworking(或一般的块)创建同步方法。一个好的方法是创建另一个方法,并将成功块中的JSON数据用作参数。

- (void)methodUsingJsonFromSuccessBlock:(id)json {
    // use the json
    NSLog(@"json from the block : %@", json); 
}

- (void)someFunction {
    AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
        success:^(NSURLRequest *request, NSHTTPURLResponse *response, id json){
            // use the json not as return data, but pass it along to another method as an argument
            [self methodUsingJsonFromSuccessBlock:json];
        }
        failure:nil];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation: operation];
}

json需要被保留在某个地方,以便实例不会被释放吗?我假设AFNetworking代码正在自动释放它。 - raidfive
在ARC下,当块正在执行时,它将被该块所保留。 - paulmelnikow
或者更现代化的方法是使用NSNotification - Raptor
无用!你知道吗,如果你的应用程序按照具体顺序发送请求,那么在大多数情况下,并不意味着应用程序会以相同的顺序处理响应?我发现的唯一方法是同步请求和PromiseKit(以及类似的库)。 - Gargo

6
值得注意的是,AFNetworking的AFClient的某些功能仍然可以以同步方式使用,这意味着您仍然可以使用一些便利的功能,例如授权头和多部分上传。
例如:
NSURLRequest *request = [self.client requestWithMethod: @"GET"
                                                  path: @"endpoint"
                                            parameters: @{}];
NSHTTPURLResponse *response = nil;
NSError *error = nil;

NSData *responseData = [NSURLConnection sendSynchronousRequest: request
                                             returningResponse: &response
                                                         error: &error];

在这种情况下,请记得检查response.statusCode,因为该方法不将HTTP失败代码视为错误。


1
使用self.client表示AFHTTPClient的实例。 - Greg M. Krsak
这正是我所需要的,谢谢。我想要一个可以从我们客户端上运行的调试器中调用的方法,该方法可以针对我们的REST后端提供类似于“curl”的查询,而无需重新实现客户端已经管理的OAUTH yack shaving。它可能也适用于测试和其他非交互式任务。 - Benjohn

4
请在您通常使用的代码下面添加以下内容:
[operation start];
[operation waitUntilFinished];
// do what you want
// return what you want

例子:

+ (NSString*) runGetRequest:(NSString*)frontPath andMethod:(NSString*)method andKeys:(NSArray*)keys andValues:(NSArray*)values
{
    NSString * pathway = [frontPath stringByAppendingString:method];
    AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:pathway]];
    NSMutableDictionary * params = [[NSMutableDictionary alloc] initWithObjects:values forKeys:keys];
    NSMutableURLRequest *request = [httpClient requestWithMethod:@"GET"
                                                            path:pathway
                                                      parameters:params];
    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    [httpClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];
    [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) 
{
            // Success happened here so do what ever you need in a async manner
} 
failure:^(AFHTTPRequestOperation *operation, NSError *error) 
{
            //error occurred here in a async manner
}];
        [operation start];
        [operation waitUntilFinished];

         // put synchronous code here

        return [operation responseString];
}

2
[operation waitUntilFinished]; 对块没有影响。 - Raptor

1

为了扩展/更新@Kasik的答案。您可以使用信号量在AFNetworking上创建一个类别,如下所示:

@implementation AFHTTPSessionManager (AFNetworking)

- (id)sendSynchronousRequestWithBaseURLAsString:(NSString * _Nonnull)baseURL pathToData:(NSString * _Nonnull)path parameters:(NSDictionary * _Nullable)params {
    __block id result = nil;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    AFHTTPSessionManager *session = [[AFHTTPSessionManager alloc]initWithBaseURL:[NSURL URLWithString:baseURL]];
    [session GET:path parameters:params progress:nil success:^(NSURLSessionDataTask *task, id responseObject) {
        result = responseObject;
        dispatch_semaphore_signal(semaphore);
    } failure:^(NSURLSessionDataTask *task, NSError *error) {
        dispatch_semaphore_signal(semaphore);
    }];
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    return result;
 }

@end

如果您在另一个AFNetwork请求的完成块中调用同步块,请确保更改completionQueue属性。如果您不更改它,同步块将在主队列上完成时调用主队列,并且会导致应用程序崩溃。
+ (void)someRequest:(void (^)(id response))completion {
    AFHTTPSessionManager *session = [[AFHTTPSessionManager alloc]initWithBaseURL:[NSURL URLWithString:@""] sessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    dispatch_queue_t queue = dispatch_queue_create("name", 0);
    session.completionQueue = queue;
    [session GET:@"path/to/resource" parameters:nil progress:nil success:^(NSURLSessionDataTask *task, id responseObject) {
     NSDictionary *data = [session sendSynchronousRequestWithBaseURLAsString:@"" pathToData:@"" parameters:nil ];
      dispatch_async(dispatch_get_main_queue(), ^{
          completion (myDict);
      });
} failure:^(NSURLSessionDataTask *task, NSError *error) {
    dispatch_async(dispatch_get_main_queue(), ^{
        completion (error);
    });
}];

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接