2011-11-01 4 views
45

Ho una funzione che utilizza AFJSONRequestOperation e desidero restituire il risultato solo dopo il successo. Potresti indicarmi la giusta direzione? Sono ancora un po 'incapace di bloccare e AFNetworking in particolare.Can AFNetworking restituisce i dati in modo sincrono (all'interno di un blocco)?

-(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. 
} 

risposta

56

Per bloccare l'esecuzione del thread principale fino al completamento dell'operazione, si potrebbe fare [operation waitUntilFinished] dopo che è aggiunto alla coda operazione. In questo caso, non sarebbe necessario il return nel blocco; impostare la variabile __block sarebbe sufficiente.

Detto questo, sconsiglio vivamente di forzare le operazioni asincrone ai metodi sincroni. È difficile orientarsi a volte, ma se c'è un modo per strutturarlo in modo asincrono, quella sarebbe quasi sicuramente la strada da percorrere.

+0

Hey matt, grazie per la risposta. Di solito uso i miei dati in modo asincrono, ma specificamente per questo devo restituire alcuni dati da un'API, quindi non vedo davvero un altro modo, a meno che tu non possa raccomandare qualche modo di agire? :) –

+0

È sempre possibile aggiungere un parametro di blocco al metodo, ad esempio '-someFunctionWithBlock:^(NSData * data) {...}'. – mattt

+0

Come ho detto che sono piuttosto un principiante con blocchi, come sarà questo aiuto? (e, cosa più importante, che cosa significa avere un parametro di blocco nel metodo?) –

11

Suggerisco di non creare un metodo sincrono con AFNetworking (o blocchi in generale). Un buon approccio è quello di creare un altro metodo e utilizzare i dati JSON dal blocco di successo come argomento.

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

'json' deve essere mantenuto da qualche parte in modo che l'istanza non sia deallocata? Suppongo che il codice AFNetworking lo autorizzi automaticamente. – raidfive

+0

Sotto ARC, mentre il blocco è in esecuzione, verrà mantenuto dal blocco. – paulmelnikow

+0

o in un modo più moderno, usa 'NSNotification'. – Raptor

6

Vale la pena notare che alcune caratteristiche del AFClient di AFNetworking possono ancora essere utilizzati in modo sincrono, il che significa che è ancora possibile utilizzare sottigliezze come ad esempio le intestazioni di autorizzazione e upload più parti.

Ad esempio:

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

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

Ricordati di controllare response.statusCode in questo caso, come questo metodo non prende in considerazione i codici di guasto HTTP come errori.

+1

Con self.client che rappresenta un'istanza di AFHTTPClient –

+0

Questo era perfetto per le mie esigenze, grazie. Volevo un metodo che potessi chiamare dal debugger in esecuzione sul nostro client ciò fornirebbe "arricciature" come le query contro il nostro backend REST, senza la necessità di reimplementare la rasatura Yack di OAUTH che il client già gestisce.È probabilmente anche adatto per test e altre attività non interattive – Benjohn

12

Sto usando i semafori per risolvere questo problema. Questo codice è implementato nella mia classe ereditata da 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; 
+0

questa è la risposta corretta! – kritzikratzi

4

Aggiungere questo sotto il codice che normalmente lavora con:

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

Esempio:

+ (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

'[operazione waitUntilFinished]; 'non ha alcun effetto sui blocchi. – Raptor

1

per espandere/aggiornamento @ risposta di Kasik. È possibile creare una categoria a AFNetworking in questo modo utilizzando semafori:

@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 

Se si chiama il blocco di sincronizzazione all'interno di un blocco di completamento di un'altra richiesta di AFNetwork, assicurarsi che si modifica la proprietà completionQueue. Se non lo si modifica, il blocco sincrono chiamerà la coda principale al completamento mentre si trova già nella coda principale e si bloccherà l'applicazione.

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