Ответ 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
. Я исправил это, спасибо, что упомянул об этом:)