这个问题分为两部分。希望有人能回复一个完整的答案。
NSOperation
是强大的对象。它们可以是两种不同的类型:非并发或并发。
第一种类型同步运行。您可以通过将非并发操作添加到NSOperationQueue
中来利用它们。后者为您创建一个线程。结果包括以并发方式运行该操作。唯一需要注意的是这种操作的生命周期。当它的main
方法完成后,它将从队列中移除。当你处理异步api时,这可能是一个问题。
那么,并发操作呢?来自Apple doc
如果你想要实现一个并发操作,也就是说,一个运行对于调用线程来说是异步的——你必须写用于异步启动操作的附加代码。例如,您可能会生成一个单独的线程,调用异步系统函数,或者做任何其他事情来确保start方法启动任务和立即返回,并且很可能在任务完成。
这对我来说几乎是很清楚的。它们异步运行。但是你必须采取适当的行动来确保他们这样做。
我不清楚的是以下几点。医生说:
注意:在OS X v10.6中,操作队列忽略返回的值并且总是从类调用操作的start方法单独的线程.
这到底是什么意思?如果我在NSOperationQueue
中添加并发操作会发生什么?
NSURLConnection
(在它的异步形式)。操作是并发的,并且包含在一个特定的队列中。
UrlDownloaderOperation * operation = [UrlDownloaderOperation urlDownloaderWithUrlString:url];
[_queue addOperation:operation];
由于NSURLConnection
需要一个循环来运行,作者在主线程中分流start
方法(所以我想将操作添加到队列中,它会产生一个不同的队列)。通过这种方式,主运行循环可以调用操作中包含的委托。
- (void)start
{
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
return;
}
[self willChangeValueForKey:@"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:@"isExecuting"];
NSURLRequest * request = [NSURLRequest requestWithURL:_url];
_connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self];
if (_connection == nil)
[self finish];
}
- (BOOL)isConcurrent
{
return YES;
}
// delegate method here...
我的问题如下。这个线程安全吗?运行循环监听源,但调用的方法在后台线程中调用。我错了吗?编辑
我已经根据Dave Dribin提供的代码完成了一些自己的测试(见1)。我注意到,正如你所写的,NSURLConnection
的回调在主线程中调用。
好的,但是现在我还是很困惑。我将尽力解释我的疑问。
为什么在并发操作中包含一个异步模式,其回调在主线程中调用?将start
方法分流到主线程,它允许在主线程中执行回调,那么队列和操作呢?我在哪里可以利用GCD提供的线程机制?
希望这是清楚的。
这是一个很长的答案,但简短的版本是,你所做的是完全好的和线程安全的,因为你已经迫使操作的重要部分在主线程上运行。
你的第一个问题是,"如果我在NSOperationQueue中添加并发操作会发生什么?"从iOS 4开始,NSOperationQueue
在幕后使用GCD。当您的操作到达队列的顶部时,它被提交给GCD, GCD管理一个私有线程池,该线程池可以根据需要动态增长和收缩。GCD分配其中一个线程来运行操作的start
方法,并保证该线程永远不会成为主线程。
当start
方法在并发操作中完成时,没有什么特别的事情发生(这就是重点)。队列将允许您的操作永远运行,直到您将isFinished
设置为YES
并执行适当的KVO willChange/didChange调用,而不管调用线程是什么。通常情况下,你会创建一个名为finish
的方法来做这件事,看起来你已经有了。
所有这些都很好,但是如果您需要观察或操作正在其上运行的线程,则需要注意一些问题。重要的是要记住:不要与GCD管理的线程混淆。你不能保证它们会在当前执行帧之后存活,你也绝对不能保证后续的委托调用(例如,来自NSURLConnection
)会发生在同一个线程上。事实上,他们可能不会。
在您的代码示例中,您已经将start
分流到主线程,因此您不需要担心后台线程(GCD或其他)。当你创建一个NSURLConnection
时,它会在当前运行循环中被调度,并且它的所有委托方法都会在该运行循环的线程中被调用,这意味着在主线程上启动连接保证了它的委托回调也会在主线程上发生。从这个意义上说,它是"线程安全的",因为除了操作本身的开始之外,后台线程几乎没有实际发生任何事情,这实际上可能是一个优势,因为GCD可以立即回收线程并将其用于其他事情。
让我们想象一下,如果你不强迫start
在主线程上运行,而只是使用GCD给你的线程,会发生什么。如果一个运行循环的线程消失,比如当它被GCD回收到它的私有池中时,它可能会永远挂起。有一些技术可以保持线程存活(例如添加一个空的NSPort
),但它们不适用于由GCD创建的线程,只适用于您自己创建的并且可以保证其生命周期的线程。
这里的危险在于,在轻负载下,您实际上可以在GCD线程上运行run循环,并认为一切都很好。一旦您开始运行许多并行操作,特别是如果您需要在中途取消它们,您将开始看到操作永远不会完成,也不会释放内存,从而泄漏内存。如果你想要完全安全,你需要创建自己的专用NSThread
,并使运行循环永远持续下去。
在现实世界中,在主线程上运行连接要容易得多。管理连接消耗很少的CPU,并且在大多数情况下不会干扰您的UI,因此完全在后台运行连接几乎没有什么好处。主线程的运行循环一直在运行,你不需要打乱它。
但是,使用上面描述的专用线程方法完全在后台运行NSURLConnection
连接是可能的。例如,查看JXHTTP,特别是类JXOperation和JXURLConnectionOperation