Подтвердить что ты не робот

Рекомендации по созданию очереди NSURLSessionTasks

Каковы наилучшие методы для создания последовательной очереди NSURLSessionTasks?

В моем случае мне нужно:

  • Получить URL-адрес внутри файла JSON (NSURLSessionDataTask)
  • Загрузите файл по этому 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];

           }
 ];

Кроме того, что запись блока завершения в другом завершении выглядит беспорядочно, я получаю ошибку EXC_BAD_ACCESS при вызове [fileDownloadTask resume]... Даже если fileDownloadTask не нуль!

Итак, что лучше всего подходит для последовательности NSURLSessionTasks?

4b9b3361

Ответ 1

Вам нужно использовать этот подход, который наиболее прямолинейный: fooobar.com/questions/276571/...

Или используйте операционную очередь и задайте задачи, зависящие друг от друга.

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

Что касается метода HTTPMaximumConnectionsPerHost

Простым способом реализации первой очереди первой очереди NSURLSessionTasks является запуск всех задач в NSURLSession, у которого свойство HTTPMaximumConnectionsPerHost установлено равным 1

HTTPMaximumConnectionsPerHost гарантирует, что для задач этого сеанса будет использоваться одно совместное соединение, но это не означает, что они будут обрабатываться последовательно.

Вы можете проверить, что на сетевом уровне с помощью http://www.charlesproxy.com/ вы обнаружите, что при настройке HTTPMaximumConnectionsPerHost ваши задачи будут по-прежнему одновременно вместе с NSURLSession объединились, а не серийно, как полагали.

Эксперимент 1:

  • Объявление NSURLSession с HTTPMaximumConnectionsPerHost до 1
  • С task1: url = download.thinkbroadband.com/20MB.zip
  • С task2: url = download.thinkbroadband.com/20MB.zip

    • вызов [task1 resume];
    • вызов [task2 resume];

Результат: task1 completeBlock вызывается, а task2 completeBlock называется

Блоки завершения могут вызываться в ожидаемом порядке, если задачи занимают одинаковое количество времени однако, если вы попытаетесь загрузить две разные вещи, используя тот же NSURLSession, что вы обнаружите, что NSURLSession делает не имеют базового заказа ваших звонков, но только завершают все, что заканчивается.

Эксперимент 2:

  • Объявление NSURLSession с HTTPMaximumConnectionsPerHost до 1
  • task1: url = download.thinkbroadband.com/20MB.zip
  • task2: url = download.thinkbroadband.com/10MB.zip(файл меньшего размера)

    • вызов [task1 resume];
    • вызов [task2 resume];

Результат: task2 completeBlock вызывается, тогда task1 completeBlock называется

В заключение вам нужно сделать заказ самостоятельно, NSURLSession не имеет никакой логики о заказе запросов, он просто вызовет завершение блокировки любых завершений сначала даже при установке максимального количества подключений на хост до 1

PS: Извините за формат сообщения. У меня недостаточно репутации для публикации скриншотов.

Ответ 2

Edit:

Как указывал mataejoon, установка HTTPMaximumConnectionsPerHost на 1 будет не гарантировать, что соединения обрабатываются последовательно. Попробуйте использовать другой подход (как в моем первоначальном ответе ниже), если вам нужна надежная последовательная очередь NSURLSessionTask.


Простым способом реализации первой очереди в очереди с очередью NSURLSessionTasks является запуск всех задач на NSURLSession, у которого есть 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) {

Ответ 3

Я использую 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];

В первом подклассе 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");
    }
}

Ответ 4

#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];