问题描述
我有一个应用程序,目前使用 NSURLConnection
进行绝大多数网络连接.我想转向 NSURLSession
因为 Apple 告诉我这是要走的路.
I have an app that currently uses NSURLConnection
for the vast majority of its networking. I would like to move to NSURLSession
because Apple tells me that is the way to go.
我的应用程序只是使用 NSURLConnection
的同步版本,通过 + (NSData *)sendSynchronousRequest:(NSURLRequest *)request returnsResponse:(NSURLResponse **)response error:(NSError**)error
类方法.我在 NSOperationQueue
上运行的 NSBlockOperation
中执行此操作,因此我不会不必要地阻塞主队列.以这种方式做事的一大优势是我可以使操作相互依赖.例如,我可以让请求数据的任务依赖于登录任务完成.
My app just uses the synchronous version of NSURLConnection
by way of the + (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
class method. I do this within a NSBlockOperation
running on an NSOperationQueue
so I am not needlessly blocking the main queue. The big advantage to doing things this way is that I can make the operations dependent on one another. For example, I can have the task that is requesting data be dependent on the login task finishing.
我在 NSURLSession
中没有看到任何对同步操作的支持.我所能找到的只是嘲笑我甚至考虑同步使用它的文章,并且我是一个阻塞线程的可怕人.美好的.但我认为没有办法让 NSURLSessionTask
相互依赖.有没有办法做到这一点?
I have not seen any support for synchronous operations within NSURLSession
. All I can find are articles deriding me for even thinking of using it synchronously and that I am a horrible person for blocking the threads. Fine. But I see no way to make NSURLSessionTask
s dependent on each other. Is there a way to do that?
或者是否有关于我将如何以不同方式做这样的事情的描述?
Or is there a description of how I would do such a thing in a different way?
推荐答案
对同步网络请求最严厉的批评保留给那些从主队列执行请求的人(正如我们所知,永远不应该阻塞主队列).但是你是在你自己的后台队列上做的,它解决了同步请求中最严重的问题.但是您正在失去异步技术提供的一些精彩功能(例如,如果需要,取消请求).
The harshest criticisms of synchronous network requests are reserved for those who do it from the main queue (as we know that one should never block the main queue). But you're doing it on your own background queue, which addresses the most egregious problem with synchronous requests. But you're losing some wonderful features that asynchronous techniques provide (e.g. cancelation of requests, if needed).
我将在下面回答您的问题(如何使 NSURLSessionDataTask
同步运行),但我真的鼓励您接受异步模式而不是对抗它们.我建议重构您的代码以使用异步模式.具体来说,如果一个任务依赖于另一个任务,只需将依赖任务的启动放在前一个任务的完成处理程序中即可.
I'll answer your question (how to make NSURLSessionDataTask
behave synchronously) below, but I'd really encourage you to embrace the asynchronous patterns rather than fighting them. I'd suggest refactoring your code to use asynchronous patterns. Specifically, if one task is dependent upon another, simply put the initiation of the dependent task in the completion handler of the prior task.
如果您在转换过程中遇到问题,请发布另一个 Stack Overflow 问题,向我们展示您的尝试,我们可以尝试帮助您.
If you have problems in that conversion, then post another Stack Overflow question, showing us what you tried, and we can try to help you out.
如果你想让异步操作同步,一个常见的模式是使用调度信号量,这样启动异步进程的线程可以在继续之前等待来自异步操作的完成块的信号.切勿从主队列执行此操作,但如果您从某个后台队列执行此操作,这可能是一种有用的模式.
If you want to make an asynchronous operation synchronous, a common pattern is to use a dispatch semaphore so your thread that initiated the asynchronous process can wait for a signal from the completion block of the asynchronous operation before continuing. Never do this from the main queue, but if you're doing this from some background queue, it can be a useful pattern.
您可以使用以下命令创建信号量:
You can create a semaphore with:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
然后您可以让异步进程的完成块向信号量发送信号:
You can then have the completion block of the asynchronous process signal the semaphore with:
dispatch_semaphore_signal(semaphore);
然后您可以让完成块之外的代码(但仍在后台队列中,而不是主队列中)等待该信号:
And you can then have the code outside of the completion block (but still on the background queue, not the main queue) wait for that signal:
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
所以,使用 NSURLSessionDataTask
,把它们放在一起,可能看起来像:
So, with NSURLSessionDataTask
, putting that all together, that might look like:
[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
可以优雅地处理它.
With NSURLConnection
(now deprecated), you have to jump through some hoops to initiate requests from a background queue, but NSURLSession
handles it gracefully.
话虽如此,使用像这样的块操作意味着操作不会响应取消事件(至少在它们运行时).所以我通常会避开这种带有块操作的信号量技术,而只是将数据任务包装在异步 NSOperation
子类中.然后您就可以享受操作带来的好处,但您也可以将它们设置为可取消.这是更多的工作,但更好的模式.
Having said that, using block operations like this means that the operations won't respond to cancellation events (while they're running, at least). So I generally eschew this semaphore technique with block operations and just wrap the data tasks in asynchronous NSOperation
subclass. Then you enjoy the benefits of operations, but you can make them cancelable, too. It's more work, but a much better pattern.
例如:
//
// 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
这篇关于NSURLSession 与 NSBlockOperation 和队列的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!