同步网络请求最受严厉批评的是那些在主队列上执行的请求(因为我们知道不应该阻塞主队列)。但是您正在自己的后台队列上执行它,这解决了同步请求中最严重的问题。但是您将失去一些异步技术提供的精彩功能(例如,如有需要,取消请求)。
我将在下面回答您的问题(如何使
NSURLSessionDataTask
表现为同步),但我真的鼓励您采用异步模式而不是反对它们。我建议您重构代码以使用异步模式。具体而言,如果一个任务依赖于另一个任务,则只需将依赖任务的启动放在先前任务的完成处理程序中即可。
如果您在转换过程中遇到问题,请发布另一个 Stack Overflow 问题,向我们展示您尝试的内容,我们可以帮助您解决问题。
如果您想将异步操作变成同步操作,一种常见的模式是使用调度信号量,以便启动异步进程的线程可以在异步操作的完成块中等待信号后继续执行。永远不要从主队列执行此操作,但如果您是从某个后台队列执行此操作,则可以使用此有用的模式。
您可以使用以下代码创建信号量:
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];
NSURLSessionTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (data) {
} else {
NSLog(@"error = %@", error);
}
dispatch_semaphore_signal(semaphore);
}];
[task resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
]);
使用NSURLConnection(现已弃用),您必须跨越一些障碍才能从后台队列启动请求,但NSURLSession可以优雅地处理它。
话虽如此,使用像这样的块操作意味着操作不会响应取消事件(至少在运行时不会)。因此,我通常避开这个信号量技术,而是将数据任务包装在异步的NSOperation子类中。然后您可以享受操作的好处,但也可以使它们可取消。这更费力,但是更好的模式。
例如:
@import Foundation;
#import "AsynchronousOperation.h"
NS_ASSUME_NONNULL_BEGIN
@interface DataTaskOperation : AsynchronousOperation
- (instancetype)initWithRequest:(NSURLRequest *)request dataTaskCompletionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))dataTaskCompletionHandler;
- (instancetype)initWithURL:(NSURL *)url dataTaskCompletionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))dataTaskCompletionHandler;
@end
NS_ASSUME_NONNULL_END
并且
#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
含义:
@import Foundation;
@interface AsynchronousOperation : NSOperation
- (void)completeOperation;
@end
并且
#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
NSURLConnection sendSynchronousRequest:
的解决方案,我已经将被接受的答案整理成了iOS类别。 - aroth