带有NSBlockOperation和队列的NSURLSession

我有一个应用程序,目前使用NSURLConnection绝大多数的networking。 我想转移到NSURLSession因为苹果告诉我这是要走的路。

我的应用程序通过+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error类方法使用NSURLConnection的同步版本。 我在一个在NSBlockOperation运行的NSOperationQueue这样做,所以我不会不必要地阻塞主队列。 以这种方式做事情的一大好处是,我可以使操作相互依赖。 例如,我可以让请求数据的任务依赖于login任务完成。

我没有看到NSURLSession任何同步操作的支持。 我能find的所有文章都是引用我,甚至想到同步使用它,而且我是一个封锁线程的可怕的人。 精细。 但是我看不NSURLSessionTask彼此依赖的方法。 有没有办法做到这一点?

还是有一种描述,我会如何以不同的方式做这样的事情?

对同步networking请求的最严厉的批评是为那些从主队列中做出来的人保留的(因为我们知道不应该阻塞主队列)。 但是你在自己的背景队列上做了这个,它解决了同步请求中最令人震惊的问题。 但是,你正在失去一些asynchronous技术提供的奇妙function(例如,如果需要取消请求)。

我将在下面回答你的问题(如何使NSURLSessionDataTask同步),但我真的鼓励你拥抱asynchronous模式,而不是与他们战斗。 我build议重构您的代码使用asynchronous模式。 具体而言,如果一个任务依赖于另一个任务,则只需将该依赖任务的启动放入先前任务的完成处理程序中即可。

如果您在转换中遇到问题,请发布另一个堆栈溢出问题,告诉我们您尝试了什么,我们可以尝试帮助您。


如果要使asynchronous操作同步,一种常见模式是使用调度信号,以便启动asynchronous进程的线程可以等待来自asynchronous操作的完成块的信号,然后再继续。 不要从主队列中做这个,但是如果你从一个背景队列中做这个,它可能是一个有用的模式。

你可以创build一个信号量:

 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 

然后你可以把asynchronous处理信号的完成块用信号量来处理:

 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会优雅地处理它。


话虽如此,使用这样的块操作意味着操作将不会响应取消事件(至less在运行时)。 所以我通常使用块操作来避开这个信号量技术,只是将数据任务包装在asynchronousNSOperation子类中。 那么你享受操作的好处,但你也可以让它们取消。 这是更多的工作,但更好的模式。

例如:

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

@Rob我鼓励你作为一个解决scheme发布你的答复,鉴于来自NSURLSession.dataTaskWithURL(_:completionHandler:)的以下文档说明:

此方法旨在作为NSURLConnection的sendAsynchronousRequest:queue:completionHandler:方法的替代scheme,并增加了支持自定义validation和取消的function。