创buildNSURLSessionTasks队列的最佳实践
什么是做一个NSURLSessionTasks
串行队列的最佳做法?
就我而言,我需要:
- 在JSON文件(
NSURLSessionDataTask
)中获取URL - 在该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只确保一个共享连接将用于该会话的任务,但这并不意味着它们将被串行处理。
实验1:
- 用HTTPMaximumConnectionsPerHost声明一个NSURLSession为1
- 随着task1:url = download.thinkbroadband.com/20MB.zip
-
随着task2:url = download.thinkbroadband.com/20MB.zip
- 调用[task1 resume];
- 调用[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( 较小的文件 )
- 调用[task1 resume];
- 调用[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];