制作NSURLSessionTasks队列的最佳实践

Eri*_*ric 22 cocoa-touch ios nsurlsession

制作串行队列的最佳做法是NSURLSessionTasks什么?

就我而言,我需要:

  1. 获取JSON文件中的URL(NSURLSessionDataTask)
  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];

           }
 ];
Run Code Online (Sandbox Code Playgroud)

除了在另一个完成中写一个完成块看起来很乱的事实,我打电话时得到一个EXC_BAD_ACCESS错误[fileDownloadTask resume]...即使fileDownloadTask不是零!

那么,最好的测序方法是NSURLSessionTasks什么?

mat*_*tes 18

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

或者使用操作队列并使任务彼此依赖

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

关于HTTPMaximumConnectionsPerHost方法

实现NSURLSessionTasks的先进先出串行队列的简单方法是在NSURLSession上运行其HTTPMaximumConnectionsPerHost属性设置为1的所有任务

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

您可以使用http://www.charlesproxy.com/在网络级别验证,您会发现在设置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下载两个不同的东西,您会发现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没有任何关于排序请求的逻辑,即使将每个主机的最大连接数设置为1,它也会先调用任何完成的completionBlock.

PS:很抱歉我发布的帖子格式没有足够的声誉来发布截图.


Eri*_*ric 13

编辑:

正如mataejoon指出的那样,设置HTTPMaximumConnectionsPerHost为1并不能保证连接是连续处理的.如果你需要一个可靠的串行队列,请尝试不同的方法(如我原来的答案)NSURLSessionTask.


实现先进先出串行队列的一种简单方法NSURLSessionTasks是在NSURLSessionHTTPMaximumConnectionsPerHost属性设置为的情况下运行所有​​任务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;
}
Run Code Online (Sandbox Code Playgroud)

然后按照您想要的顺序向其添加任务.

NSURLSessionDataTask *sizeTask = 
[[[self class] session] dataTaskWithURL:url 
                          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
Run Code Online (Sandbox Code Playgroud)


Pet*_*odd 0

我使用 NSOperationQueue (正如欧文建议的那样)。将 NSURLSessionTasks 放入 NSOperation 子类中并设置任何依赖项。依赖任务会等到它们所依赖的任务完成后才运行,但不会检查状态(成功或失败),因此添加一些逻辑来控制流程。

就我而言,第一个任务检查用户是否拥有有效的帐户,并在必要时创建一个帐户。在第一个任务中,我更新 NSUserDefault 值以指示帐户有效(或存在错误)。第二个任务检查 NSUserDefault 值,如果一切正常,则使用用户凭据将一些数据发布到服务器。

(将 NSURLSessionTasks 粘在单独的 NSOperation 子类中也使我的代码更易于导航)

将 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];
Run Code Online (Sandbox Code Playgroud)

在第一个 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];            
    }    
}
Run Code Online (Sandbox Code Playgroud)

在下一个 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");
    }
}
Run Code Online (Sandbox Code Playgroud)