AFNetworking - 为什么它会产生一个网络请求线程?

时间:2012-12-12 16:18:06

标签: objective-c afnetworking nsoperation nsoperationqueue nsthread

我正在尝试更好地理解操作和线程,并查看AFNetworking的AFURLConnectionOperation子类,例如,真实世界的源代码。

我目前的理解是当NSOperation的实例被添加到操作队列时,该队列除其他外,管理负责执行操作的线程。在Apple的NSOperation文档中,它指出即使子类为YES返回-isConcurrent,操作也将始终在一个单独的线程上启动(截至10.6)。

基于Apple在线程编程指南并发编程指南中的强大语言,管理线程似乎最好留给{{1的内部实现}}

但是,AFNetworking的NSOperationQueue子类会生成一个新的AFURLConnectionOperation,并且操作的NSThread方法的执行被推送到此网络请求线程中。为什么?为什么这个网络请求线程是必要的?这是一种防御性编程技术,因为该库旨在供广大受众使用吗?调试库的消费者是不是更麻烦?在专用线程上进行所有网络活动是否有(微妙的)性能优势?

(1月26日新增)
在Dave Dribin的blog post中,他演示了如何使用NSURLConnection的具体示例将操作移回主线程。

我的好奇心来自Apple的线程编程指南中的以下部分:

  

保持您的主题合理忙碌。
  如果您决定创建和   手动管理线程,记住线程消耗宝贵的系统   资源。你应该尽力确保你完成任何任务   分配给线程是合理的长寿和高效的。在   同时,你不应该害怕终止线程   他们大部分时间都闲着。线程使用了大量的   内存,其中一些有线,所以释放空闲线程不仅有帮助   减少应用程序的内存占用,它还可以释放更多内容   物理内存供其他系统进程使用。

在我看来,AFNetworking的网络请求线程并没有“保持相当忙碌”;它正在运行一个无限的while循环来处理网络I / O.但是,请注意,这就是问题的关键 - 我不知道,我只是在猜测。

-main有关操作,线程(运行循环?)和/或GCD的具体问题的任何见解或解构都非常有助于填补我理解的空白。

1 个答案:

答案 0 :(得分:6)

这是一个有趣的问题,答案是关于NSOperationNSURLConnection如何互动和协同工作的语义。

NSURLConnection本身就是一个异步任务。这一切都发生在后台,并定期调用其委托结果。当你启动一个NSURLConnection时,它会使用它所安排的runloop来调度委托回调,因此runloop必须始终在你正在执行NSURLConnection的线程上运行。

因此,-start上的方法AFURLConnectionOperation将始终必须在操作完成之前返回,以便它可以接收回调。这要求AFURLConnectionOperation是异步操作。

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

  

对于与当前线程异步运行的操作,属性值为YES,对于在当前线程上同步运行的操作,属性值为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的代码,显示他们在所有runloop模式下在AFNetwokring线程的runloop上安排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线程上的runloop而不是mainRunloop,因为从该线程调用了-operationDidStart方法。作为奖励,我们也可以在后台线程的runloop上运行outputStream

现在AFURLConnectionOperation等待NSURLConnection回调并更新自己的NSOperation状态变量(cancelledfinishedexecuting)本身网络请求进展。 AFNetworking线程重复地旋转其runloop,以便NSURLConnections可能从AFURLConnectionOperations计划调用它们的回调,并且AFURLConnectionOperation对象可以对它们作出反应。

  

如果您始终计划使用队列来执行操作,则将它们定义为同步更为简单。但是,如果手动执行操作,则可能需要将操作对象定义为异步操作。定义异步操作需要更多工作,因为您必须使用KVO通知监视任务的持续状态并报告该状态的更改。但是,如果要确保手动执行的操作不会阻塞调用线程,则定义异步操作非常有用。

另请注意,您也可以在NSOperation之前使用NSOperationQueue-start,并在-isFinished返回YES之前观察AFURLConnectionOperation。如果将NSURLConnection实现为同步操作并阻止当前线程等待NSURLConnection完成它将永远不会完成,因为NSOperation会在当前的runloop上调度其回调,这不会“因为我们会阻止它,所以正在运行。因此,为了支持使用AFURLConnectionOperation的有效方案,我们必须使completionBlock异步。

问题解答

  • 是的,AFNetworking创建了一个用于安排所有连接的线程。线程创建很昂贵。 (这也是创建GCD的部分原因.GCD会为您运行一个线程池,并根据需要在不同的线程上调度块,而无需自己创建,销毁和管理线程)

  • 处理不在后台AFNetworking线程上完成。 AFNetworking使用NSOperation finished属性进行处理,该处理在YES设置为AFHTTPRequestOperation时执行。

  

无法保证完成块的确切执行上下文,但通常是辅助线程。因此,您不应该使用此块来执行任何需要非常特定的执行上下文的工作。相反,您应该将该工作分流到应用程序的主线程或能够执行此操作的特定线程。例如,如果您有一个用于协调操作完成的自定义线程,则可以使用完成块来ping该线程。

HTTP连接的后处理在- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { self.completionBlock = ^{ //... dispatch_async(http_request_operation_processing_queue(), ^{ //... 中处理。此类创建一个调度队列,专门用于在后台转换响应对象,并将工作分流到该队列。见here

AFURLConnectionOperation

我想这可能会引发他们写- (void)setDelegateQueue:(NSOperationQueue*) queue NS_AVAILABLE(10_7, 5_0); 不能创建线程的问题。我认为答案是肯定的,因为有这个API

+networkRequestThread

这意味着在特定操作队列上安排代理回调,而不是使用runloop。但是,我们正在查看AFNetworking的遗留部分,并且该API仅适用于iOS 5和OS X 10.7。看看Github对AFURLRequestOperation的责备观点,我们可以看到mattt实际上是在2011年iPhone 4s和iOS 5宣布的当天巧妙地编写了NSURLConnection方法!因此我们可以推断该线程存在,因为在编写时我们可以看到创建一个线程并在其上调度连接是在后台运行时从后台NSOperation接收回调的唯一方法dispatch_once子类。

  • 使用NSURLConnectionOperation函数创建线程。 (请参阅我建议添加的额外代码剪切)此函数确保其运行的块中包含的代码仅在应用程序的生命周期中运行一次。 AFNetworking线程在需要时创建,然后在应用程序的生命周期内持续存在

  • 当我写AFURLConnectionOperation时,我的意思是{{1}}。我更正了,谢谢你提到它:)