AFNetworking - Почему это порождает поток сетевых запросов?

Я пытаюсь понять Operations и Threads лучше и посмотрел на подкласс AFNetworking AFURLConnectionOperation, например, в реальном мире, исходный код.

Мое настоящее понимание - когда экземпляры NSOperation добавляются в очередь операций, очередь, между прочим, управляет потоком, ответственным за выполнение операции. В документации Apple NSOperation указывается, что даже если подклассы возвращают YES для -isConcurrent, операция всегда будет запускаться в отдельном потоке (начиная с 10.6).

Основываясь на сильном языке Apple в руководстве по программированию нитей и руководстве по программированию Concurrency, кажется, что управление потоком лучше всего оставить во внутренней реализации NSOperationQueue.

Однако подкласс AFNetworking AFURLConnectionOperation создает новый NSThread, а выполнение метода -main выполняется в этом потоке сетевого запроса. Зачем? Зачем нужен этот сетевой запрос? Является ли это защитным методом программирования, потому что библиотека предназначена для использования широкой аудиторией? Разве это меньше хлопот для потребителей библиотеки для отладки? Существует ли (тонкое) преимущество в производительности для всей сетевой активности в выделенном потоке?

(Добавлено 26 января)
В сообщении в блоге Дэйвом Дрибином он иллюстрирует, как переместить операцию обратно в основной поток, используя конкретный пример NSURLConnection.

Мое любопытство проистекает из следующего раздела в Руководстве по программированию Apple Thread:

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

Мне кажется, что поток запросов сетевой сети AFNetworking не "поддерживается достаточно занятым"; он запускает бесконечный цикл while для обработки сетевых операций ввода-вывода. Но, видите, что суть этих вопросов - я не знаю, и я только догадываюсь.

Любое понимание или деконструкция AFURLConnectionOperation с особым отношением к операциям, потокам (циклы цикла?) и/или GCD было бы очень полезно для заполнения пробелов в моем понимании.

Ответ 1

Его интересный вопрос и ответ - все о семантике взаимодействия NSOperation и NSURLConnection и совместной работы.

An NSURLConnection сам по себе является асинхронной задачей. Все это происходит в фоновом режиме и периодически вызывает его делегата с результатами. Когда вы запускаете NSURLConnection, он рассылает обратные вызовы делегатов, используя runloop, на котором он запланирован, поэтому runloop всегда должен выполняться в потоке, в котором вы выполняете NSURLConnection on.

Поэтому метод -start на нашем AFURLConnectionOperation всегда должен возвращаться до завершения операции, чтобы он мог получать обратные вызовы. Для этого требуется асинхронная операция.

from: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSOperation_class/index.html

Значение свойства - YES для операций, которые выполняются асинхронно по отношению к текущему потоку или NO для операций, которые выполняются синхронно в текущем потоке. Значением по умолчанию этого свойства является NO.

Но AFURLConnectionOperation переопределяет этот метод и возвращает YES, как и следовало ожидать. Тогда из описания класса мы видим:

Когда вы вызываете метод старта асинхронной операции, этот метод может вернуться до завершения соответствующей задачи. Асинхронный рабочий объект отвечает за планирование своей задачи в отдельном потоке. Операция может сделать это, начав новый поток напрямую, вызывая асинхронный метод или отправив блок для очереди отправки для выполнения. На самом деле не имеет значения, продолжена ли операция, когда управление возвращается к вызывающему, только чтобы оно продолжалось.

AFNetworking создает единый сетевой поток с использованием метода класса, в котором он планирует все объекты NSURLConnection (и их последующие обратные вызовы делегатов). Вот код из AFURLConnectionOperation

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}

+ (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
        [_networkRequestThread start];
    });

    return _networkRequestThread;
}

Вот код из AFURLConnectionOperation, показывающий им планирование NSURLConnection на runloop потока AFNetwokring во всех режимах runloop

- (void)start {
    [self.lock lock];
    if ([self isCancelled]) {
        [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    } else if ([self isReady]) {
        self.state = AFOperationExecutingState;

        [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    }
    [self.lock unlock];
}

- (void)operationDidStart {
    [self.lock lock];
    if (![self isCancelled]) {
        self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        for (NSString *runLoopMode in self.runLoopModes) {
            [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
            [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
        }

        //...
    }
    [self.lock unlock];
}

здесь [NSRunloop currentRunloop] извлекает runloop в потоке AFNetworking вместо mainRunloop, поскольку из этого потока вызывается метод -operationDidStart. В качестве бонуса мы также запускаем outputStream в фоновом потоке runloop.

Теперь AFURLConnectionOperation ждет ответные запросы NSURLConnection и обновляет свои собственные переменные состояния NSOperation (cancelled, finished, executing) по мере продвижения сетевого запроса. Нить AFNetworking многократно повторяет свою runlooo, так как NSURLConnections из потенциально многих AFURLConnectionOperations планируют свои обратные вызовы, которые они вызывают, и объекты AFURLConnectionOperation могут реагировать на них.

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

Также обратите внимание, что вы также можете использовать NSOperation без NSOperationQueue, вызывая -start и наблюдая его, пока -isFinished не вернет YES. Если AFURLConnectionOperation был реализован как синхронная операция и заблокировал текущий поток, ожидающий завершения NSURLConnection, он бы никогда не закончил, так как NSURLConnection планировал бы свои обратные вызовы в текущей runloop, которые не будут выполняться, как мы бы блокируя его. Поэтому для поддержки этого допустимого сценария использования NSOperation мы должны сделать асинхронный AFURLConnectionOperation.

Ответы на вопросы

  • Да, AFNetworking создает один поток, который он использует для планирования всех подключений. Создание темы дорого. (отчасти это потому, что был создан GCD. GCD поддерживает пул потоков для вас и отправляет блоки по различным потокам по мере необходимости без необходимости создавать, уничтожать и управлять потоками самостоятельно).

  • Обработка не выполняется в фоновом потоке AFNetworking. AFNetworking использует свойство completionBlock для NSOperation для выполнения своей обработки, которая выполняется, когда finished установлено на YES.

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

пост-обработка HTTP-соединений обрабатывается в AFHTTPRequestOperation. Этот класс создает очередь отправки специально для преобразования объектов ответа в фоновом режиме и отключает работу в этой очереди. см. здесь

- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                              failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
    self.completionBlock = ^{
        //...
        dispatch_async(http_request_operation_processing_queue(), ^{
            //...

Я предполагаю, что это задает вопрос, могли ли они написать AFURLConnectionOperation, чтобы не создавать поток. Я думаю, что да, так как есть этот API

- (void)setDelegateQueue:(NSOperationQueue*) queue NS_AVAILABLE(10_7, 5_0);

Предназначен для планирования обратных вызовов делегатов в конкретной очереди операций, а не для использования runloop. Но поскольку мы смотрим на устаревшую часть AFNetworking, и этот API доступен только в iOS 5 и OS X 10.7. Глядя на вид вины на Github для AFURLRequestOperation, мы можем видеть, что mattt фактически написал метод +networkRequestThread по совпадению в день, когда iPhone 4s и iOS 5 были объявлены еще в 2011 году! Поэтому мы можем рассуждать о том, что поток существует, потому что в то время, когда он был написан, мы видим, что создание потока и планирование ваших подключений на нем было единственным способом получить обратные вызовы из NSURLConnection в фоновом режиме во время работы в асинхронном NSOperation подкласс.

  • поток создается с помощью функции dispatch_once. (см. добавленный дополнительный код, добавленный мной, как вы предложили). Эта функция гарантирует, что код, заключенный в блок, который он запускает, будет запускаться только один раз в течение жизни приложения. Нить AFNetworking создается, когда это необходимо, а затем сохраняется для срока службы приложения.

  • Когда я написал NSURLConnectionOperation, я имел в виду AFURLConnectionOperation. Я исправил это, спасибо, что упомянул об этом:)