创buildNSURLSessionTasks队列的最佳实践

什么是做一个NSURLSessionTasks串行队列的最佳做法?

就我而言,我需要:

  1. 在JSON文件( NSURLSessionDataTask )中获取URL
  2. 在该URL下载该文件( 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进行sorting的最好方法是NSURLSessionTasks

您需要使用这个最直接的方法: https : //stackoverflow.com/a/31386206/2308258

或者使用操作队列,使任务相互依赖

================================================== =====================

关于HTTPMaximumConnectionsPerHost方法

实现NSURLSessionTasks的先入先出串行队列的简单方法是在NSMLSession中运行所有任务,该NSURLSession的HTTPMaximumConnectionsPerHost属性设置为1

HTTPMaximumConnectionsPerHost只确保一个共享连接将用于该会话的任务,但这并不意味着它们将被串行处理。

您可以使用http://www.charlesproxy.com/validation在networking级别,您将会发现,在设置HTTPMaximumConnectionsPerHost时,您的任务仍将由NSURLSession同时启动,而不是被认为是串行的。

实验1:

  • 用HTTPMaximumConnectionsPerHost声明一个NSURLSession为1
  • 随着task1:url = download.thinkbroadband.com/20MB.zip
  • 随着task2:url = download.thinkbroadband.com/20MB.zip

    1. 调用[task1 resume];
    2. 调用[task2 resume];

结果 :调用task1 completionBlock,然后调用task2 completionBlock

完成块可能会按照您期望的顺序调用,以防任务花费相同的时间, 如果您尝试使用相同的NSURLSession下载两个不同的东西,您将发现NSURLSession没有任何基础的调用顺序,先完成任何完成。

实验2:

  • 用HTTPMaximumConnectionsPerHost声明一个NSURLSession为1
  • task1:url = download.thinkbroadband.com/20MB.zip
  • task2:url = download.thinkbroadband.com/10MB.zip( 较小的文件

    1. 调用[task1 resume];
    2. 调用[task2 resume];

结果 :调用task2 completionBlock,然后调用task1 completionBlock

总之,你需要自己完成顺序,NSURLSession没有任何关于sorting请求的逻辑,即使将每个主机的最大连接数设置为1,它也会直接调用completionBlock,

PS:抱歉的post格式我没有足够的声望张贴截图。

编辑:

正如mataejoon所指出的那样,将HTTPMaximumConnectionsPerHost设置为1 不能保证连接是连续处理的。 如果您需要一个可靠的NSURLSessionTask串行队列,请尝试一种不同的方法(如以下原始答案中所述)。


实现NSURLSessionTasks的先进先出串行队列的NSURLSessionTasks是在NSURLSessionTasks中运行HTTPMaximumConnectionsPerHost属性设置为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) { 

我使用NSOperationQueue(正如Owen所build议的)。 将NSURLSessionTasks放在NSOperation子类中,并设置任何依赖关系。 相关的任务将等到他们依赖的任务在运行之前完成,但不会检查状态(成功或失败),因此添加一些逻辑来控制进程。

在我的情况下,第一个任务检查用户是否有一个有效的帐户,并在必要时创build一个。 在第一个任务中,我更新了一个NSUserDefault值来表示帐户有效(或者有错误)。 第二个任务检查NSUserDefault的值,如果所有的确定使用用户凭据将一些数据发布到服务器。

(在单独的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 post数据中:

 @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"); } } 
 #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];