NSOperation и NSURLConnection мистифицированы

Я пытаюсь загрузить несколько изображений с некоторого сервера, используя NSOperation и NSOperationQueue. Мой главный вопрос заключается в том, какая разница между фрагментом кода ниже, и эта ссылка http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/, производительность мудрая? Я предпочитаю второе решение, потому что у нас намного больше контроля над операциями, он намного чище, и если соединение не удается, вы можете справиться с ним должным образом.

Если я попытаюсь загрузить около 300 изображений с сервера с приведенным ниже кодом, у моего приложения будет довольно большая задержка, и если я запустил приложение и сразу же перейду на главный экран, вернитесь в приложение, Я потерплю крах, потому что не было достаточно времени, чтобы приложение снова активировалось. Если я раскомментирую [queue setMaxConcurrentOperationCount: 1], пользовательский интерфейс реагирует, и он не будет сбой, если он войдет в фоновый режим и вернется на передний план.

Но если я реализую нечто похожее на ссылку выше, мне не нужно беспокоиться о настройке maxConcurrentOperationCount, значение по умолчанию - это нормально. Все отзывчиво, без сбоев, и кажется, что все очереди быстрее завершаются.

Итак, это подводит меня к моему второму вопросу, почему [queue setMaxConcurrentOperationCount: 1] имеет такое большое влияние в моем коде ниже? Из документации я думал, что оставить maxConcurrentOperationCount по умолчанию по умолчанию, и это просто говорит очереди определить, какое наилучшее значение должно быть основано на определенных факторах.

Это мой первый пост в Stack Overflow, поэтому, надеюсь, это имеет смысл, спасибо за любую помощь!

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//[queue setMaxConcurrentOperationCount:1];

for(NSURL *URL in URLArray) {
    [queue addOperationWithBlock:^{
        NSHTTPURLResponse *response = nil;
        NSError *error = nil;
        NSData * data = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:URL] returningResponse:&response error:&error];

        if(!error && data) {
            [data writeToFile:path atomically:YES];
        }
    }];
}

Ответ 1

Я собираюсь решить их в обратном порядке. Вы спрашиваете:

Итак, это подводит меня к моему второму вопросу, почему [queue setMaxConcurrentOperationCount: 1] имеет такое большое влияние в моем коде ниже? Из документации я думал, что оставить maxConcurrentOperationCount по умолчанию по умолчанию, и это просто говорит очереди определить, какое наилучшее значение должно быть основано на определенных факторах.

С NSURLConnection вы не можете одновременно загружать более четырех или пяти подключений. Таким образом, если вы не устанавливаете maxConcurrentOperationCount, очередь операций не знает, что вы имеете дело с NSURLConnection, и поэтому, когда вы добавляете 300 NSOperation объектов в очередь, очередь будет пытаться запустить очень большой число из них (64-х, я думаю) одновременно. Но так как запросы только 4 или 5 NSURLConnection могут выполняться одновременно, остальные из них, которые были запущены в очередь, будут ждать, пока доступно одно из четырех или пяти возможных подключений, и с таким количеством запросов на загрузку вполне вероятно, что многие из них будут тайм-аут и неудача.

Используя maxConcurrentOperationCount of 1, это применит довольно жесткое решение этой проблемы, только запустив один за раз. Я бы предложил компромисс, а именно maxConcurrentOperationCount of 4, который имеет степень concurrency (и огромный прирост производительности), но не так много, что мы рискуем иметь время соединения и выйти из строя.

Возвращаясь к Дейву Друбину NSOperation, он значительно улучшился по сравнению с вашим synchronousRequest, завернутым в операцию. Сказав это, он пренебрег рассмотрением довольно простой функции одновременных запросов, а именно отмены. Вы должны включить проверку, чтобы проверить, была ли операция отменена, и если это так, отмените соединение:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    if ([self isCancelled]) {
        [connection cancel];
        [self finish];
        return;
    }

    [_data appendData:data];
}

Аналогично, когда он должен делать это в методе start тоже.

- (void)start
{
    // The Apple docs say "Always check for cancellation before launching the task."

    if ([self isCancelled]) {
        [self willChangeValueForKey:@"isFinished"];
        _isFinished = YES;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }

    if (![NSThread isMainThread])
    {
        [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
        return;
    }

    NSLog(@"opeartion for <%@> started.", _url);

    [self willChangeValueForKey:@"isExecuting"];
    _isExecuting = YES;
    [self didChangeValueForKey:@"isExecuting"];

    NSURLRequest * request = [NSURLRequest requestWithURL:_url];
    _connection = [[NSURLConnection alloc] initWithRequest:request
                                                  delegate:self];
    if (_connection == nil)
        [self finish];
}

Я мог бы предложить другие стилистические улучшения к примеру Дэйва, но все это мелкие вещи, и я думаю, что он получил большую часть большого места на картинке. Невозможность проверить отмену была единственной очевидной большой проблемой, которая выскочила на меня.

В любом случае, для обсуждения параллельных операций см. раздел "Конфигурирование операций для параллельного выполнения" Concurrency Руководство по программированию.

Кроме того, при тестировании огромных загрузок, подобных этим, я бы посоветовал вам протестировать свое приложение с помощью Network Link Conditioner (доступного для Mac/simulator в качестве загрузки, доступной в разделе "Оборудование IO-инструментов" на "Xcode" - "Open Developer Tool" - "Дополнительные инструменты для разработчиков", если вы включите устройство iOS для разработки, в настройках также есть настройка настройки сетевой ссылки в разделе "Общие" - "Разработчик" ). Многие из этих проблем, связанных с таймаутом, не проявляются, когда мы тестируем наши приложения в нашем высоко оптимизированном сценарии нашей среды разработки. Важно использовать средство связи с сетью для моделирования менее реалистичных сценариев реального мира.

Ответ 2

Я предпочитаю второе решение, потому что у нас намного больше контроля над операциями

Если вы ссылаетесь на свое собственное решение, то на самом деле имеет место обратное:

Из-за метода синхронного удобства sendSynchronousRequest: у вас практически нет возможности выполнить более практические требования, такие как аутентификация, улучшенная обработка ошибок и настраиваемая обработка данных, а также многие другие функции, обычно требуемые в любом приложении, которое не является демонстрацией или игрушечное приложение.

Однако обломки - это отсутствие способности отменять операции. Вы не можете отменить операцию блока после ее запуска. И также непонятно, как вы хотите отменить цикл for. Итак, как только цикл запущен, вы не можете его остановить.

Вы можете искать более сложные (и более современные) подходы в Интернете и на SO.

Ответ 3

Когда очередь MaxConcurrentOperationCount равнa > 1, есть вероятность, что очередь будет заблокирована и разблокирована по завершению операций, в то время как вы все еще добавляете больше заданий в очередь, но когда вы установите ее на 1, очередь получает более полно, пока работа не начнет работать, что означает, что цикл URLArray заканчивается быстрее (избегая постоянной блокировки).

Если я правильно помню NSOperationQueue api, вы можете "приостановить" и "возобновить" его... Я рекомендую вам установить его "приостановлено" до тех пор, пока вы не добавите все задания, а затем установите его повторно.