Может ли 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
будет достаточной.
Тем не менее, я решительно отговаривал бы заставлять асинхронные операции синхронным методам. Это сложно сделать иногда вокруг, но если вы каким-то образом структурируете это, чтобы быть асинхронным, это почти наверняка будет способом.
Ответ 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];
}
Ответ 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 как ошибки.
Ответ 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];
}
Ответ 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);
});
}];