Может ли AFNetworking возвращать данные синхронно (внутри блока)?

#ios #objective-c-blocks #synchronous #afnetworking

#iOS #objective-c-блоки #синхронный #afnetworking

Вопрос:

У меня есть функция, использующая 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.
}
 

Ответ №1:

Чтобы заблокировать выполнение основного потока до завершения операции, вы могли бы сделать [operation waitUntilFinished] это после его добавления в очередь операций. В этом случае вам не понадобится return в блоке; достаточно установить __block переменную.

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

Комментарии:

1. Привет, Мэтт, спасибо за ответ. Обычно я использую свои данные асинхронно, но специально для этого мне приходится возвращать некоторые данные из API, поэтому я не вижу другого способа, если только вы не можете порекомендовать какой-то способ действий? 🙂

2. Вы всегда можете добавить параметр блока к методу, например -someFunctionWithBlock:^(NSData *data) {...} .

3. К сожалению, трюк waitUntilFinished у меня не работает. У меня есть пара бизнес-методов, которые являются синхронными по своей природе. Жаль, что AFNetworking полностью игнорирует такой вариант использования.

4. Я подозреваю waitUntilFinished , что для некоторых этот трюк не работает, потому что блоки успеха и сбоя (по умолчанию) выполняются с использованием dispatch_async в основной очереди после завершения операции. Если вы не выполняете внутри цикла выполнения, например, модульный тест, то программа может завершиться досрочно, не дав GCD возможности выполнить обратные вызовы.

5. Я думаю, что в идеале любой сетевой 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 resu<
 

Ответ №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];
}
 

Комментарии:

1. json Нужно ли где-то сохранять, чтобы экземпляр не был освобожден? Я предполагаю, что код AFNetworking автоматически удаляет его.

2. При ARC, пока блок выполняется, он будет сохранен блоком.

3. или более современный способ, используйте NSNotification .

4. Бесполезно! Знаете ли вы, что если ваше приложение отправляет запросы в определенном порядке, то в большинстве случаев это не означает, что приложение будет обрабатывать ответы в том же порядке? Единственными способами, которые я обнаружил, являются синхронные запросы и promisekit (и аналогичные библиотеки

Ответ №4:

Стоит отметить, что некоторые функции AFClient от AFNetworking все еще могут использоваться синхронно, что означает, что вы все еще можете использовать такие тонкости, как заголовки авторизации и многостраничные загрузки.

Например:

 NSURLRequest *request = [self.client requestWithMethod: @"GET"
                                                  path: @"endpoint"
                                            parameters: @{}];
NSHTTPURLResponse *response = nil;
NSError *error = nil;

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

Не забудьте проверить response.statusCode в этом случае, поскольку этот метод не рассматривает коды сбоев HTTP как ошибки.

Комментарии:

1. С self.client, представляющим экземпляр AFHTTPClient

2. Это было идеально для моих нужд, спасибо. Мне нужен был метод, который я мог бы вызывать из отладчика, работающего на нашем клиенте, который предоставлял бы запросы типа «curl» к нашему бэкэнду REST, без необходимости переопределять OAUTH yack shaving, которым уже управляет клиент. Вероятно, это также подходит для тестов и других неинтерактивных задач.

Ответ №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];
}
 

Комментарии:

1. [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 resu<
 }

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