制作NSURLSessionTasks
的串行队列的最佳做法是什么?
就我而言,我需要:
NSURLSessionDataTask
)NSURLSessionDownloadTask
)这是我到目前为止所拥有的:
session = [NSURLSession sharedSession];
//Download the JSON:
NSURLRequest *dataRequest = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *task =
[session dataTaskWithRequest:dataRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//Figure out the URL of the file I want to download:
NSJSONSerialization *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
NSURL *downloadURL = [NSURL urlWithString:[json objectForKey:@"download_url"]];
NSURLSessionDownloadTask *fileDownloadTask =
[session downloadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:playlistURL]]
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
NSLog(@"completed!");
}];
[fileDownloadTask resume];
}
];
除了在另一个完成中写一个完成块看起来很乱的事实,我在调用[fileDownloadTask resume]
时收到EXC_BAD_ACCESS错误...即使fileDownloadTask
不是零!
那么,排序NSURLSessionTasks
的最佳方式是什么?
答案 0 :(得分:18)
您需要使用最直接的方法:https://stackoverflow.com/a/31386206/2308258
或者使用操作队列并使任务相互依赖
=============================================== ========================
关于HTTPMaximumConnectionsPerHost方法
实现NSURLSessionTasks的先进先出串行队列的简单方法是在NSURLSession上运行其HTTPMaximumConnectionsPerHost属性设置为1的所有任务
HTTPMaximumConnectionsPerHost仅确保一个共享连接将用于该会话的任务,但这并不意味着它们将被串行处理。
您可以使用http://www.charlesproxy.com/在网络级别验证,您会发现在设置HTTPMaximumConnectionsPerHost时,您的任务仍将由NSURLSession同时启动,而不是按照相同的顺序启动。
实验1:
使用task2:url = download.thinkbroadband.com/20MB.zip
结果:调用task1 completionBlock然后调用task2 completionBlock
如果您的任务花费相同的时间,则可能会按预期的顺序调用完成块如果您尝试使用相同的NSURLSession下载两个不同的东西,您会发现NSURLSession会执行没有你的电话的任何潜在订购,但只完成任何完成。
实验2:
task2:url = download.thinkbroadband.com/10MB.zip(较小的文件)
结果:调用task2 completionBlock然后调用task1 completionBlock
总之,您需要自己进行排序,NSURLSession没有任何关于排序请求的逻辑,即使将每个主机的最大连接数设置为1
PS:很抱歉该帖子的格式我没有足够的声誉来发布截图。
答案 1 :(得分:13)
修改强>
正如mataejoon指出的那样,将HTTPMaximumConnectionsPerHost
设置为1将不保证连接是按顺序处理的。 如果您需要NSURLSessionTask
的可靠序列队列,请尝试不同的方法(如我原来的答案)。
实现NSURLSessionTasks
的先进先出串行队列的简单方法是在NSURLSession
属性设置为1
的{{1}}上运行所有任务}:
+ (NSURLSession *)session
{
static NSURLSession *session = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
[configuration setHTTPMaximumConnectionsPerHost:1];
session = [NSURLSession sessionWithConfiguration:configuration];
});
return session;
}
然后按照您想要的顺序向添加任务。
NSURLSessionDataTask *sizeTask =
[[[self class] session] dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
答案 2 :(得分:1)
#import "SessionTaskQueue.h"
@interface SessionTaskQueue ()
@property (nonatomic, strong) NSMutableArray * sessionTasks;
@property (nonatomic, strong) NSURLSessionTask * currentTask;
@end
@implementation SessionTaskQueue
- (instancetype)init {
self = [super init];
if (self) {
self.sessionTasks = [[NSMutableArray alloc] initWithCapacity:15];
}
return self;
}
- (void)addSessionTask:(NSURLSessionTask *)sessionTask {
[self.sessionTasks addObject:sessionTask];
[self resume];
}
// call in the completion block of the sessionTask
- (void)sessionTaskFinished:(NSURLSessionTask *)sessionTask {
self.currentTask = nil;
[self resume];
}
- (void)resume {
if (self.currentTask) {
return;
}
self.currentTask = [self.sessionTasks firstObject];
if (self.currentTask) {
[self.sessionTasks removeObjectAtIndex:0];
[self.currentTask resume];
}
}
@end
并像这样使用
__block __weak NSURLSessionTask * wsessionTask;
use_wself();
wsessionTask = [[CommonServices shared] doSomeStuffWithCompletion:^(NSError * _Nullable error) {
use_sself();
[self.sessionTaskQueue sessionTaskFinished:wsessionTask];
...
}];
[self.sessionTaskQueue addSessionTask:wsessionTask];
答案 3 :(得分:0)
我使用NSOperationQueue(正如Owen所建议的那样)。将NSURLSessionTasks放在NSOperation子类中并设置任何依赖项。相关任务将等到它们所依赖的任务在运行之前完成,但不会检查状态(成功或失败),因此添加一些逻辑来控制该过程。
在我的情况下,第一个任务检查用户是否拥有有效帐户并在必要时创建一个帐户。在第一个任务中,我更新NSUserDefault值以指示帐户有效(或存在错误)。第二个任务检查NSUserDefault值,如果所有OK都使用用户凭据将一些数据发布到服务器。
(在单独的NSOperation子类中粘贴NSURLSessionTasks也使我的代码更容易导航)
将NSOperation子类添加到NSOperationQueue并设置任何依赖项:
NSOperationQueue *ftfQueue = [NSOperationQueue new];
FTFCreateAccount *createFTFAccount = [[FTFCreateAccount alloc]init];
[createFTFAccount setUserid:@"********"]; // Userid to be checked / created
[ftfQueue addOperation:createFTFAccount];
FTFPostRoute *postFTFRoute = [[FTFPostRoute alloc]init];
[postFTFRoute addDependency:createFTFAccount];
[ftfQueue addOperation:postFTFRoute];
在第一个NSOperation子类中检查服务器上是否存在帐户:
@implementation FTFCreateAccount
{
NSString *_accountCreationStatus;
}
- (void)main {
NSDate *startDate = [[NSDate alloc] init];
float timeElapsed;
NSString *ftfAccountStatusKey = @"ftfAccountStatus";
NSString *ftfAccountStatus = (NSString *)[[NSUserDefaults standardUserDefaults] objectForKey:ftfAccountStatusKey];
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setValue:@"CHECKING" forKey:ftfAccountStatusKey];
// Setup and Run the NSURLSessionTask
[self createFTFAccount:[self userid]];
// Hold it here until the SessionTask completion handler updates the _accountCreationStatus
// Or the process takes too long (possible connection error)
while ((!_accountCreationStatus) && (timeElapsed < 5.0)) {
NSDate *currentDate = [[NSDate alloc] init];
timeElapsed = [currentDate timeIntervalSinceDate:startDate];
}
if ([_accountCreationStatus isEqualToString:@"CONNECTION PROBLEM"] || !_accountCreationStatus) [self cancel];
if ([self isCancelled]) {
NSLog(@"DEBUG FTFCreateAccount Cancelled" );
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setValue:@"ERROR" forKey:ftfAccountStatusKey];
}
}
在下一个NSOperation发布数据中:
@implementation FTFPostRoute
{
NSString *_routePostStatus;
}
- (void)main {
NSDate *startDate = [[NSDate alloc] init];
float timeElapsed;
NSString *ftfAccountStatusKey = @"ftfAccountStatus";
NSString *ftfAccountStatus = (NSString *)[[NSUserDefaults standardUserDefaults] objectForKey:ftfAccountStatusKey];
if ([ftfAccountStatus isEqualToString:@"ERROR"])
{
// There was a ERROR in creating / accessing the user account. Cancel the post
[self cancel];
} else
{
// Call method to setup and run json post
// Hold it here until a reply comes back from the operation
while ((!_routePostStatus) && (timeElapsed < 3)) {
NSDate *currentDate = [[NSDate alloc] init];
timeElapsed = [currentDate timeIntervalSinceDate:startDate];
NSLog(@"FTFPostRoute time elapsed: %f", timeElapsed);
}
}
if ([self isCancelled]) {
NSLog(@"FTFPostRoute operation cancelled");
}
}