Ответ 1
Самая суровая критика синхронных сетевых запросов зарезервирована для тех, кто делает это из основной очереди (поскольку мы знаем, что никогда не следует блокировать основную очередь). Но вы делаете это в своей собственной фоновой очереди, которая затрагивает наиболее вопиющую проблему с синхронными запросами. Но вы теряете некоторые замечательные функции, которые предоставляют асинхронные методы (например, отмена запросов, если это необходимо).
Я отвечу на ваш вопрос (как сделать NSURLSessionDataTask
вести себя синхронно) ниже, но я бы действительно рекомендовал вам использовать асинхронные шаблоны, а не бороться с ними. Я бы предложил рефакторинг кода для использования асинхронных шаблонов. В частности, если одна задача зависит от другой, просто поставьте инициализацию зависимой задачи в обработчик завершения предыдущей задачи.
Если у вас есть проблемы с этим преобразованием, отправьте еще один вопрос о переполнении стека, показывая нам, что вы пробовали, и мы можем попытаться помочь вам.
Если вы хотите сделать асинхронную операцию синхронной, общий шаблон - использовать семафор отправки, чтобы ваш поток, инициировавший асинхронный процесс, мог дождаться сигнала из блока завершения асинхронной операции, прежде чем продолжить. Никогда не делайте этого из основной очереди, но если вы делаете это из некоторой фоновой очереди, это может быть полезным шаблоном.
Семафор можно создать с помощью
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
Затем вы можете заполнить блок асинхронного процесса семафором с помощью:
dispatch_semaphore_signal(semaphore);
И тогда вы можете получить код за пределами блока завершения (но все еще в фоновом режиме, а не в главной очереди), ожидая этого сигнала:
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
Итак, с NSURLSessionDataTask
, помещая все вместе, это может выглядеть так:
[queue addOperationWithBlock:^{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURLSession *session = [NSURLSession sharedSession]; // or create your own session with your own NSURLSessionConfiguration
NSURLSessionTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (data) {
// do whatever you want with the data here
} else {
NSLog(@"error = %@", error);
}
dispatch_semaphore_signal(semaphore);
}];
[task resume];
// but have the thread wait until the task is done
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// now carry on with other stuff contingent upon what you did above
]);
С NSURLConnection
(теперь устаревшим) вам нужно перепрыгнуть через некоторые обручи, чтобы инициировать запросы из фоновой очереди, но NSURLSession
обрабатывает его изящно.
Сказав, что использование таких блок-операций означает, что операции не будут реагировать на события отмены (хотя они работают, по крайней мере). Поэтому я вообще избегаю этой семафорной техники с блочными операциями и просто обертываю задачи данных в асинхронном подклассе NSOperation
. Тогда вам нравятся преимущества операций, но вы можете сделать их отменными. Это больше работает, но гораздо лучше.
Например:
//
// DataTaskOperation.h
//
// Created by Robert Ryan on 12/12/15.
// Copyright © 2015 Robert Ryan. All rights reserved.
//
@import Foundation;
#import "AsynchronousOperation.h"
NS_ASSUME_NONNULL_BEGIN
@interface DataTaskOperation : AsynchronousOperation
/// Creates a operation that retrieves the contents of a URL based on the specified URL request object, and calls a handler upon completion.
///
/// @param request A NSURLRequest object that provides the URL, cache policy, request type, body data or body stream, and so on.
/// @param dataTaskCompletionHandler The completion handler to call when the load request is complete. This handler is executed on the delegate queue. This completion handler takes the following parameters:
///
/// @returns The new session data operation.
- (instancetype)initWithRequest:(NSURLRequest *)request dataTaskCompletionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))dataTaskCompletionHandler;
/// Creates a operation that retrieves the contents of a URL based on the specified URL request object, and calls a handler upon completion.
///
/// @param url A NSURL object that provides the URL, cache policy, request type, body data or body stream, and so on.
/// @param dataTaskCompletionHandler The completion handler to call when the load request is complete. This handler is executed on the delegate queue. This completion handler takes the following parameters:
///
/// @returns The new session data operation.
- (instancetype)initWithURL:(NSURL *)url dataTaskCompletionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))dataTaskCompletionHandler;
@end
NS_ASSUME_NONNULL_END
и
//
// DataTaskOperation.m
//
// Created by Robert Ryan on 12/12/15.
// Copyright © 2015 Robert Ryan. All rights reserved.
//
#import "DataTaskOperation.h"
@interface DataTaskOperation ()
@property (nonatomic, strong) NSURLRequest *request;
@property (nonatomic, weak) NSURLSessionTask *task;
@property (nonatomic, copy) void (^dataTaskCompletionHandler)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error);
@end
@implementation DataTaskOperation
- (instancetype)initWithRequest:(NSURLRequest *)request dataTaskCompletionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))dataTaskCompletionHandler {
self = [super init];
if (self) {
self.request = request;
self.dataTaskCompletionHandler = dataTaskCompletionHandler;
}
return self;
}
- (instancetype)initWithURL:(NSURL *)url dataTaskCompletionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))dataTaskCompletionHandler {
NSURLRequest *request = [NSURLRequest requestWithURL:url];
return [self initWithRequest:request dataTaskCompletionHandler:dataTaskCompletionHandler];
}
- (void)main {
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:self.request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
self.dataTaskCompletionHandler(data, response, error);
[self completeOperation];
}];
[task resume];
self.task = task;
}
- (void)completeOperation {
self.dataTaskCompletionHandler = nil;
[super completeOperation];
}
- (void)cancel {
[self.task cancel];
[super cancel];
}
@end
Где:
//
// AsynchronousOperation.h
//
@import Foundation;
@interface AsynchronousOperation : NSOperation
/// Complete the asynchronous operation.
///
/// This also triggers the necessary KVO to support asynchronous operations.
- (void)completeOperation;
@end
и
//
// AsynchronousOperation.m
//
#import "AsynchronousOperation.h"
@interface AsynchronousOperation ()
@property (nonatomic, getter = isFinished, readwrite) BOOL finished;
@property (nonatomic, getter = isExecuting, readwrite) BOOL executing;
@end
@implementation AsynchronousOperation
@synthesize finished = _finished;
@synthesize executing = _executing;
- (instancetype)init {
self = [super init];
if (self) {
_finished = NO;
_executing = NO;
}
return self;
}
- (void)start {
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[self main];
}
- (void)completeOperation {
self.executing = NO;
self.finished = YES;
}
#pragma mark - NSOperation methods
- (BOOL)isAsynchronous {
return YES;
}
- (BOOL)isExecuting {
@synchronized(self) {
return _executing;
}
}
- (BOOL)isFinished {
@synchronized(self) {
return _finished;
}
}
- (void)setExecuting:(BOOL)executing {
@synchronized(self) {
if (_executing != executing) {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
}
}
- (void)setFinished:(BOOL)finished {
@synchronized(self) {
if (_finished != finished) {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
}
}
@end