这是一个有趣的问题,答案是关于 NSOperation 和 NSURLConnection 如何交互和协同工作的语义。
NSURLConnection 本身就是一个异步任务。这一切都发生在后台,并定期调用其委托与结果。当您启动 NSURLConnection 时,它会使用安排在其上的 runloop 来安排委托回调,因此 runloop 必须始终在您正在执行 NSURLConnection 的线程上运行。
因此,AFURLConnectionOperation 上的方法 -start 将始终必须在操作完成之前返回,以便它可以接收回调。这要求AFURLConnectionOperation 是一个异步操作。
来自:https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSOperation_class/index.html
对于与当前线程异步运行的操作,property 的值为 YES,对于在当前线程上同步运行的操作,property 的值为 NO。此属性的默认值为 NO。
但是AFURLConnectionOperation 覆盖了这个方法并返回YES,正如我们所期望的那样。然后我们从类描述中看到:
当您调用异步操作的 start 方法时,该方法可能会在相应任务完成之前返回。异步操作对象负责在单独的线程上调度其任务。该操作可以通过直接启动新线程、调用异步方法或将块提交到调度队列执行来实现。当控制权返回给调用者时操作是否正在进行实际上并不重要,只要它可能正在进行。
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 的代码,显示他们在所有运行循环模式下在 AFNetwokring 线程的运行循环上调度 NSURLConnection
- (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] 检索AFNetworking 线程而不是mainRunloop 上的runloop,因为从该线程调用-operationDidStart 方法。作为奖励,我们还可以在后台线程的 runloop 上运行 outputStream。
现在AFURLConnectionOperation 等待NSURLConnection 回调并随着网络请求的进行更新其自己的NSOperation 状态变量(cancelled、finished、executing)本身。 AFNetworking 线程反复旋转它的运行循环,以便当NSURLConnections 来自潜在的许多AFURLConnectionOperations 调度它们的回调时,它们被调用并且AFURLConnectionOperation 对象可以对它们做出反应。
如果您总是计划使用队列来执行您的操作,将它们定义为同步会更简单。但是,如果您手动执行操作,您可能希望将操作对象定义为异步的。定义异步操作需要更多的工作,因为您必须监控任务的持续状态并使用 KVO 通知报告该状态的变化。但在您希望确保手动执行的操作不会阻塞调用线程的情况下,定义异步操作很有用。
还请注意,您还可以通过调用 -start 并观察它直到 -isFinished 返回 YES 来使用不带 NSOperationQueue 的 NSOperation。如果AFURLConnectionOperation 被实现为同步操作并阻塞当前线程等待NSURLConnection 完成它永远不会真正完成,因为NSURLConnection 将在当前runloop 上安排它的回调,它不会像我们那样运行阻止它。因此,为了支持这种使用NSOperation 的有效场景,我们必须使AFURLConnectionOperation 异步。
问题解答
是的,AFNetworking 创建一个线程,用于调度所有连接。线程创建是昂贵的。 (这也是创建 GCD 的部分原因。GCD 会为您保留一个运行的线程池,并根据需要在不同的线程上分派块,而无需自己创建、销毁和管理线程)
处理不在后台 AFNetworking 线程上完成。 AFNetworking 使用NSOperation 的completionBlock 属性进行处理,当finished 设置为YES 时执行。
无法保证完成块的确切执行上下文,但通常是辅助线程。因此,您不应使用此块来执行任何需要非常特定的执行上下文的工作。相反,您应该将该工作分流到应用程序的主线程或能够执行此操作的特定线程。例如,如果您有一个自定义线程来协调操作的完成,您可以使用完成块来 ping 该线程。
HTTP 连接的后处理在AFHTTPRequestOperation 中处理。此类创建一个调度队列,专门用于在后台转换响应对象,并将工作分流到该队列上。见here
- (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);
这意味着将您的委托回调安排在特定的操作队列上,而不是使用运行循环。但是当我们看到 AFNetworking 的遗留部分时,该 API 仅在 iOS 5 和 OS X 10.7 中可用。查看 Github 上 AFURLRequestOperation 的责备视图,我们可以看到,mattt 实际上在 2011 年发布 iPhone 4s 和 iOS 5 的那天巧合地编写了 +networkRequestThread 方法!因此,我们可以推断线程存在,因为在编写它的时候,我们可以看到创建一个线程并在其上调度您的连接是在异步运行时在后台接收来自NSURLConnection 的回调的唯一方法@987654376 @子类。