2011-11-01 9 views
45

Mam funkcję przy użyciu AFJSONRequestOperation i chcę zwrócić wynik tylko po sukcesie. Czy możesz wskazać mi właściwy kierunek? Wciąż jestem trochę nieświadomy bloki i AFNetworking specjalnie.Czy AFNetworking może zwracać dane synchronicznie (wewnątrz bloku)?

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

Odpowiedz

56

Aby zablokować wykonanie głównego wątku aż do zakończenia operacji, można zrobić [operation waitUntilFinished] po to dodane do kolejki pracy. W takim przypadku nie potrzebujesz return w bloku; ustawienie zmiennej __block wystarczy.

Powiedziałem, że zdecydowanie odradzam wymuszanie operacji asynchronicznych na metodach synchronicznych. Czasami trudno jest sobie poradzić z głową, ale jeśli istnieje sposób, w jaki można by to ustrukturyzować jako asynchroniczne, prawie na pewno byłoby to możliwe.

+0

Hej mat, dzięki za odpowiedź. Zwykle używam moich danych asynchronicznie, ale specjalnie w tym celu muszę zwrócić niektóre dane z API, więc naprawdę nie widzę innego sposobu, chyba że możesz polecić jakiś sposób działania? :) –

+0

Zawsze można dodać parametr bloku do metody, np. '-someFunctionWithBlock:^(NSData * data) {...}'. – mattt

+0

Jak powiedziałem, jestem całkiem początkującym z blokami, w jaki sposób to pomoże? (i co ważniejsze, co to znaczy mieć parametr blokowy w metodzie?) –

11

Sugerowałbym, aby nie wykonywać metody synchronicznej z AFNetworking (lub ogólnie blokami). Dobrym podejściem jest to, że tworzysz inną metodę i używasz danych json z bloku sukcesu jako argumentu.

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

Czy 'json' musi być gdzieś zatrzymany, aby instancja nie została zwolniona? Zakładam, że kod AFNetworking automatycznie go inicjuje. – raidfive

+0

Pod ARC, podczas wykonywania bloku, zostanie zatrzymany przez blok. – paulmelnikow

+0

lub bardziej nowoczesny sposób, użyj 'NSNotification'. – Raptor

6

Warto zauważyć, że niektóre funkcje AFNetworking za AFClient mogą być nadal stosowane w sposób synchroniczny, co oznacza, że ​​można nadal korzystać z subtelności, takie jak nagłówki zezwolenia i wieloczęściowych przesyłania.

Na przykład:

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

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

Pamiętaj, aby sprawdzić response.statusCode w tym przypadku, jak ta metoda nie uwzględnia kody błędów HTTP jak błędy.

+1

Z self.client reprezentujące wystąpienie AFHTTPClient –

+0

To był idealny dla moich potrzeb, dziękuję. Chciałem sposób mogę zadzwonić z debugger działa na naszym klientem która zapewniłaby „zwijać” jak zapytaniami przeciwko naszej REST zaplecza, bez konieczności reimplement OAuth Yack golenie, że klient już udaje. jest to prawdopodobnie również nadaje się do testów a także innych interaktywnych zadań. – Benjohn

12

Używam semaforów do rozwiązania tego problemu. Ten kod jest zaimplementowany w mojej własnej klasie odziedziczonej po 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

to jest poprawna odpowiedź! – kritzikratzi

4

Dodaj ten poniżej kodu normalnie pracować:

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

Przykład:

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

'[operacja waitUntilFinished]; 'nie ma wpływu na bloki. – Raptor

1

aby rozwinąć/aktualizacji @ odpowiedź Kasik użytkownika. Można utworzyć kategorię na AFNetworking jak tak wykorzystaniem semaforów:

@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 

Jeśli dzwonisz synchronizacji blok wewnątrz bloku realizacji innego wniosku AFNetwork, upewnij się zmienić właściwość completionQueue. Jeśli go nie zmienisz, blok synchroniczny wywoła główną kolejkę po zakończeniu, gdy znajduje się już w głównej kolejce i zawiesi twoją aplikację.

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