我最近刚试了一个示例应用程序试图让我的头完全缠绕NSRunLoop
。我编写的示例通过NSOperation
创建了一个简单的辅助线程。辅助线程执行一些任务,例如处理NSTimer
以及使用NSStream
的一些基本流程。这两个输入源都需要正确配置NSRunLoop
才能执行。
我的问题是这个。最初我在辅助线程中有一些看起来像这样的代码:
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:connectTimeout
target:self
selector:@selector(connectionConnectedCheck:)
userInfo:nil
repeats:NO];
[myRunLoop addTimer:self.connectTimer forMode:NSDefaultRunLoopMode]; // added source here
[myRunLoop run];
[NSStream getStreamsToHostNamed:relayHost port:relayPort inputStream:&inputStream outputStream:&outputStream];
if ((inputStream != nil) && (outputStream != nil))
{
sendState = kSKPSMTPConnecting;
isSecure = NO;
[inputStream retain];
[outputStream retain];
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop: myRunLoop //[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
[outputStream scheduleInRunLoop: myRunLoop //[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
[inputStream open];
[outputStream open];
self.inputString = [NSMutableString string];
return YES;
}
现在,使用上面的代码,事件永远不会处理。不在currentRunLoop
。之后我做了一些可怕的事情,因为这只是一项教育练习,并将其修改为在NSRunLoop
mainRunLoop
下运行。像魔术一样工作。但是,我几乎可以肯定,根据我的主线程,在辅助线程中运行循环是在错误的的10个不同级别上。
所以我的问题分为两部分,我希望没问题。
为了让次要线程运行并通过运行循环响应事件,我应用的小'hack'可能出错?
配置辅助线程以侦听所有基于事件/定时器的源的正确方法是什么,所以我不必执行第1步。
感谢所有人的洞察力。
答案 0 :(得分:6)
以相反的顺序回答您的问题:
2
。你有两个问题。 -[NSRunLoop run]
的文档说:
如果没有输入源或计时器 附加到运行循环,这个方法 立即退出;否则,它运行 接收器在 反复
NSDefaultRunLoopMode
调用runMode:beforeDate:
。其他 言语,这种方法有效地开始 一个处理数据的无限循环 来自运行循环的输入源和 定时器。
因此,使用线程自己的运行循环,可能没有为运行循环定义输入源,因此它会立即返回。如果存在,则运行循环无限循环,并且在该点之后的其余代码永远不会被执行。
为了使事情正常工作,您的运行循环首先需要一些输入源,然后需要定期运行以检查事件。请注意,您不希望使用[NSRunLoop run]
,因为您永远无法控制回来。相反,我建议在return YES
之前设置一个循环,它继续运行运行循环,直到线程被取消,或者直到你完成流数据。这将允许运行循环在数据到达时处理数据。像这样:
while (!done) {
[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
}
[NSRunLoop runUntilDate:]
处理事件直到指定日期,然后将控制权返回给您的程序,以便您可以随心所欲。
1
。这是有效的,因为主线程的运行循环是定期运行,因此正在处理事件。但是,如果您的主线程被阻止,您的数据将停止到达。如果主线程正在等待第二个线程的数据,这可能会特别糟糕。此外,NSRunLoop不是线程安全的:
警告:NSRunLoop类是 通常不被认为是 线程安全及其方法应该 只能在上下文中调用 当前的线程。你永远不应该 试着调用一个方法 在一个运行的NSRunLoop对象 不同的线程,这样做可能 导致意外的结果。 (来自NSRunLoop文档。)
Apple的线程编程指南有一个名为Run Loop Management的部分,它在某种程度上解释了所有这些。这不是我读过的最清晰的文档,但是如果你正在使用run loop,那么这是一个很好的起点。
答案 1 :(得分:2)
你还记得运行runloop吗?
[[NSRunLoop currentLoop] run]
答案 2 :(得分:1)
首先,scheduledTimer...
方法应该自动将计时器添加到当前运行循环中。如果使用addTimer:
初始化程序创建计时器,则只需使用initWithFireDate...
方法。我怀疑两次添加计时器会导致问题,但这是可能的。
run
NSRunLoop
方法不应该返回,直到没有更多事件源或显式退出循环。这意味着您需要在调用run
之前安排任何事件源。将[myRunLoop run]
电话移至代码示例的最后。 (另外,显然,消除return
语句)
最重要的是,如果[NSRunLoop run]
正在返回,那么您没有安排事件源。使用调试器逐步执行代码。如果您看到它超过了[NSRunLoop run]
来电,那么您可以确定输入来源存在问题。