这是一个两部分问题。希望有人可以回答完整的答案。
NSOperation
是强大的对象。它们可以有两种不同的类型:非并发或并发。
第一种类型同步运行。您可以通过将非并发操作添加到NSOperationQueue
中来利用它们。后者为您创建一个线程。结果包括以并发方式运行该操作。唯一需要注意的是这种操作的生命周期。当其main
方法完成时,它将从队列中删除。处理异步API时,这可能是一个问题。
现在,并发操作怎么样?来自Apple doc
如果要实现并发操作,即运行的操作 与调用线程异步 - 你必须写 用于异步启动操作的附加代码。例如, 你可能会产生一个单独的线程,调用一个异步系统 函数,或执行任何其他操作以确保start方法启动 任务并立即返回,并且很有可能在此之前返回 任务完成了。
这对我来说几乎是清楚的。它们以异步方式运行。但是你必须采取适当的行动来确保他们这样做。
我不清楚的是以下内容。 Doc说:
注意:在OS X v10.6中,操作队列忽略返回的值 isConcurrent并始终从a调用操作的start方法 单独的线程。
它的真正含义是什么? 如果我在NSOperationQueue
中添加并发操作会怎样?
然后,在这篇文章Concurrent Operations中,并发操作用于通过NSURLConnection
(以异步形式)下载某些HTTP内容。操作是并发的,并包含在特定队列中。
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提供的线程机制?
希望这很清楚。
答案 0 :(得分:9)
这是一个很长的答案,但简短的版本是你正在做的事情是完全正确和线程安全的,因为你强迫操作的重要部分在主线程上运行。
你的第一个问题是,“如果我在NSOperationQueue中添加并发操作会怎么样?” As of 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线程上运行一个运行循环,并认为一切都很好。一旦你开始运行许多并行操作,特别是如果你需要在中途取消它们,你将开始看到从未完成的操作,并且永远不会释放内存,泄漏内存。如果你想要完全安全,你需要创建自己的专用NSThread
并保持运行循环永远。
在现实世界中,执行您正在执行的操作并在主线程上运行连接要容易得多。管理连接消耗的CPU非常少,并且在大多数情况下不会干扰您的UI,因此在后台完全运行连接几乎没有什么好处。主线程的运行循环始终在运行,您不需要弄乱它。
但是,可以使用上述专用线程方法在后台完全运行NSURLConnection
连接。有关示例,请查看JXHTTP,特别是课程JXOperation和JXURLConnectionOperation