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

ede*_*y05 10 objective-c nsoperation nsthread nsoperationqueue afnetworking

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

我目前的理解是当实例NSOperation被添加到操作队列时,队列等管理负责执行操作的线程.在苹果公司的文档,NSOperation它指出,即使子类返回YES-isConcurrent操作将始终在一个单独的线程来启动(为10.6).

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

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

(
1月26日添加)在Dave Dribin 的博客文章中,他演示了如何使用NSURLConnection的具体示例将操作移回主线程.

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

保持你的线程合理繁忙.
如果您决定手动创建和管理线程,请记住线程占用宝贵的系统资源.您应该尽力确保为线程分配的任何任务都是合理的长寿和高效的.与此同时,您不应该害怕终止花费大部分时间闲置的线程.线程使用大量内存,其中一些是有线的,因此释放空闲线程不仅有助于减少应用程序的内存占用,还可以释放更多物理内存供其他系统进程使用.

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

AFURLConnectionOperation对操作,线程(运行循环?)和/或GCD的具体问题的任何洞察或解构都将非常有利于填补我理解的空白.

jac*_*ash 6

这是一个有趣的问题,答案是关于如何NSOperation以及如何NSURLConnection互动和协同工作的语义.

An NSURLConnection本身就是一个异步任务.这一切都发生在后台,并定期调用其委托结果.当你启动NSURLConnection它时,它使用运行循环来调度委托回调,因此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;
}
Run Code Online (Sandbox Code Playgroud)

以下代码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];
}
Run Code Online (Sandbox Code Playgroud)

这里[NSRunloop currentRunloop]检索AFNetworking线程,而不是在runloop mainRunloop-operationDidStart方法是从该线程调用.作为奖励,我们也可以outputStream在后台线程的runloop上运行.

现在,AFURLConnectionOperation等待NSURLConnection回调,并更新自己的NSOperation状态变量(cancelled,finished,executing作为网络请求的进展)本身.AFNetworking线程反复旋转其runloop,以便NSURLConnections从可能的许多AFURLConnectionOperations调度它们的回调调用它们,并且AFURLConnectionOperation对象可以对它们作出反应.

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

另请注意,您也可以使用NSOperationNSOperationQueue通过调用-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(), ^{
            //...
Run Code Online (Sandbox Code Playgroud)

我想这可能会引发他们写不出来的问题AFURLConnectionOperation.我认为答案是肯定的,因为有这个API

- (void)setDelegateQueue:(NSOperationQueue*) queue NS_AVAILABLE(10_7, 5_0);
Run Code Online (Sandbox Code Playgroud)

这意味着在特定操作队列上安排您的委托回调,而不是使用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.我纠正了,谢谢你提到它:)