web-dev-qa-db-fra.com

AFNetworking peut-il renvoyer des données de manière synchrone (à l'intérieur d'un bloc)?

J'ai une fonction utilisant AFJSONRequestOperation, et je souhaite retourner le résultat seulement après le succès. Pourriez-vous me diriger dans la bonne direction? Je suis encore un peu désemparé avec les blocs et AFNetworking en particulier.

-(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.
}
46
Shai Mishali

Pour bloquer l'exécution du thread principal jusqu'à la fin de l'opération, vous pouvez faire [operation waitUntilFinished] après l'ajout à la file d'attente des opérations. Dans ce cas, vous n'auriez pas besoin du return dans le bloc; définition du __block variable suffirait.

Cela dit, je déconseille fortement de forcer les opérations asynchrones aux méthodes synchrones. Il est parfois difficile de se mettre en tête, mais s'il existe un moyen de structurer cela de manière asynchrone, ce serait presque certainement la voie à suivre.

56
mattt

J'utilise des sémaphores pour résoudre ce problème. Ce code est implémenté dans ma propre classe héritée de 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;
12
Kasik

Je suggérerais que vous ne fassiez pas une méthode synchrone avec AFNetworking (ou les blocs en général). Une bonne approche consiste à créer une autre méthode et à utiliser les données json du bloc de réussite comme argument.

- (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];
}
11
Ikhsan Assaat

Il convient de noter que certaines fonctionnalités de AFClient d'AFNetworking peuvent toujours être utilisées de manière synchrone, ce qui signifie que vous pouvez toujours utiliser des subtilités telles que les en-têtes d'autorisation et les téléchargements en plusieurs parties.

Par exemple:

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

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

N'oubliez pas de vérifier response.statusCode dans ce cas, car cette méthode ne considère pas les codes d'échec HTTP comme des erreurs.

6
joerick

Ajoutez ceci sous le code avec lequel vous travaillez normalement:

[operation start];
[operation waitUntilFinished];
// do what you want
// return what you want

Exemple:

+ (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];
}
4
user2976703

Pour développer/mettre à jour la réponse de @ Kasik. Vous pouvez créer une catégorie sur AFNetworking comme si vous utilisiez des sémaphores:

@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

Si vous appelez le bloc de synchronisation à l'intérieur d'un bloc d'achèvement d'une autre demande AFNetwork, assurez-vous de modifier la propriété completionQueue. Si vous ne le modifiez pas, le bloc synchrone appellera la file d'attente principale à la fin alors qu'il se trouve déjà dans la file d'attente principale et plantera votre application.

+ (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);
    });
}];
1
Mark Bourke