我正在尝试更好地理解操作和线程,并查看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的具体问题的任何见解或解构都非常有助于填补我理解的空白。
答案 0 :(得分:6)
这是一个有趣的问题,答案是关于NSOperation
和NSURLConnection
如何互动和协同工作的语义。
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
状态变量(cancelled
,finished
,executing
)本身网络请求进展。 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}}。我更正了,谢谢你提到它:)