【问题标题】:AFNetworking - Why does it spawn a network request thread?AFNetworking - 为什么它会产生一个网络请求线程?
【发布时间】:2012-11-30 09:46:48
【问题描述】:

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

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

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

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

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

我的好奇心来自 Apple 的 Thread Programming Guide 中的以下部分:

保持您的线程相当繁忙。
如果您决定创建并 手动管理线程,记住线程消耗宝贵的系统 资源。您应该尽最大努力确保您完成的任何任务 分配给线程的寿命相当长且富有成效。在 同时,你不应该害怕终止那些 他们大部分时间都在闲置。线程使用大量的 内存,其中一些是有线的,因此释放空闲线程不仅有帮助 减少应用程序的内存占用,它还释放更多 供其他系统进程使用的物理内存。

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

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

【问题讨论】:

  • 有趣的问题。 + 1. 我认为您需要更正此声明:在 Apple 的 NSOperation 文档中指出,即使子类为 -isConcurrent 返回 YES,操作也将始终在单独的线程上启动(从 10.6 开始)。我>
  • 来自文档:然而,在 OS X v10.5 中,只有当它们的 isConcurrent 方法返回 NO 时,操作才会在单独的线程上执行。如果该方法返回 YES,则操作对象应创建自己的线程(或启动一些异步操作);队列没有为其提供线程。
  • 表示(从10.6开始)如果将操作添加到队列中,将YES设置为isConcurrent属性无效。对于回复,我想我们需要等待@mattt。
  • 我问@mattt 那天我问的问题。
  • @edelaney05 当您可以调用 [operation start] 方法并将工作移交给那里的专用线程时,我也不明白使用 NSOperationQueue 蹦床的原因。你得到问题的答案了吗?

标签: objective-c afnetworking nsoperation nsoperationqueue nsthread


【解决方案1】:

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

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 状态变量(cancelledfinishedexecuting)本身。 AFNetworking 线程反复旋转它的运行循环,以便当NSURLConnections 来自潜在的许多AFURLConnectionOperations 调度它们的回调时,它们被调用并且AFURLConnectionOperation 对象可以对它们做出反应。

如果您总是计划使用队列来执行您的操作,将它们定义为同步会更简单。但是,如果您手动执行操作,您可能希望将操作对象定义为异步的。定义异步操作需要更多的工作,因为您必须监控任务的持续状态并使用 KVO 通知报告该状态的变化。但在您希望确保手动执行的操作不会阻塞调用线程的情况下,定义异步操作很有用。

还请注意,您还可以通过调用 -start 并观察它直到 -isFinished 返回 YES 来使用不带 NSOperationQueueNSOperation。如果AFURLConnectionOperation 被实现为同步操作并阻塞当前线程等待NSURLConnection 完成它永远不会真正完成,因为NSURLConnection 将在当前runloop 上安排它的回调,它不会像我们那样运行阻止它。因此,为了支持这种使用NSOperation 的有效场景,我们必须使AFURLConnectionOperation 异步。

问题解答

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

  • 处理不在后台 AFNetworking 线程上完成。 AFNetworking 使用NSOperationcompletionBlock 属性进行处理,当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 @子类。

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

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

【讨论】:

  • 答案写得很好!当您在编写 AFNetworking 使用类方法创建单个网络线程时,它会在其上调度所有 NSURLConnection 对象(及其生成的委托回调) 我只会添加属于该线程的代码 sn-p创建(参见AFURLConnectionOperation 的第 163 到 182 行)
  • 从我强调的那几行中我了解到,AFNetworking 只创建一个线程,在不同的操作中重用。创建线程是一项非常昂贵的任务。我错了吗?
  • 我没有关注 AFNetworking 代码,但我认为该线程用于 JSON 解析或图像处理,因为它们可能会耗费时间。无论如何,如果一个操作完成,那个线程会发生什么?它是在某处停止还是继续循环?
  • 最后,NSURLConnectionOperation,你的意思是NSURLConnection操作。如果是这样,我会重命名它。
  • 请注意,它是一个递归锁(因此可以从同一个线程多次锁定),这是因为 AFURLConnectionOperation 的属性可能会从主线程或 AFNetworking 线程发生变异或任何其他线程或队列。
猜你喜欢
  • 1970-01-01
  • 2018-04-19
  • 1970-01-01
  • 2021-10-08
  • 1970-01-01
  • 1970-01-01
  • 2019-01-17
  • 2018-07-21
  • 2020-01-29
相关资源
最近更新 更多