【问题标题】:Can AFNetworking return data synchronously (inside a block)?AFNetworking 可以同步返回数据(在一个块内)吗?
【发布时间】:2011-12-19 16:05:19
【问题描述】:

我有一个使用 AFJSONRequestOperation 的函数,我希望仅在成功后返回结果。你能指出我正确的方向吗?我对块和 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.
}

【问题讨论】:

    标签: ios objective-c-blocks synchronous afnetworking


    【解决方案1】:

    要阻止主线程的执行直到操作完成,您可以在将其添加到操作队列后执行[operation waitUntilFinished]。在这种情况下,您不需要块中的return;设置__block 变量就足够了。

    也就是说,我强烈反对将异步操作强制用于同步方法。有时很难理解,但如果有任何方法可以将其构建为异步,那几乎肯定是要走的路。

    【讨论】:

    • 嗨,马特,感谢您的回复。通常我确实异步使用我的数据,但特别是为此我必须从 API 返回一些数据,所以我真的没有看到另一种方式,除非你可以推荐一些行动方式? :)
    • 你总是可以在方法中添加一个块参数,比如-someFunctionWithBlock:^(NSData *data) {...}
    • 不幸的是,waitUntilFinished 技巧对我不起作用。我有几个本质上是同步的业务方法。很遗憾 AFNetworking 完全忽略了这样的用例。
    • 我怀疑waitUntilFinished 技巧对某些人不起作用,因为成功和失败块(默认情况下)在操作完成后在主队列上使用dispatch_async 执行。如果你没有在 runloop 中执行,例如单元测试,那么程序可能会提前退出而不给 GCD 运行回调的机会。
    • 我认为理想情况下,任何网络 SDK 都应该允许用户选择是否需要异步操作;它不应该强制使用特定的模型,尽管它可能会建议一个。
    【解决方案2】:

    我正在使用信号量来解决这个问题。这段代码是在我自己的继承自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;
    

    【讨论】:

      【解决方案3】:

      我建议您不要使用 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 代码正在自动释放它。
      • ARC下,block在执行时会被block保留。
      • 或更现代的方式,使用NSNotification
      • 没用!您是否知道,如果您的应用程序以具体的顺序发送请求,那么在大多数情况下,这并不意味着应用程序将以相同的顺序处理响应?我发现的唯一方法是同步请求和 promisekit(以及类似的库
      【解决方案4】:

      值得注意的是,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 失败代码视为错误。

      【讨论】:

      • 用 self.client 代表 AFHTTPClient 的一个实例
      • 这非常适合我的需求,谢谢。我想要一种方法,我可以从客户端上运行的调试器中调用该方法,该方法可以针对我们的 REST 后端提供类似“curl”的查询,而无需重新实现客户端已经管理的 OAUTH yack saving。它可能也适用于测试和其他非交互式任务。
      【解决方案5】:

      将其添加到您通常使用的代码下方:

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

      【讨论】:

      • [operation waitUntilFinished]; 对块没有影响。
      【解决方案6】:

      扩展/更新@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);
          });
      }];
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-04-08
        • 1970-01-01
        • 2014-05-24
        • 1970-01-01
        • 1970-01-01
        • 2015-05-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多