我试图更好地理解运行循环应用于Mac应用程序(NSRunLoop),但这可能也是一个更普遍的问题。NSRunLoop文档中写道:
您的代码提供了驱动运行循环的
while
或for
循环。在循环中,您使用运行循环对象来"运行"接收事件并调用已安装的处理程序的事件处理代码
文档中有这样一个代码示例:
BOOL shouldKeepRunning = YES;
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
因此,代码一直调用运行循环,直到决定终止为止。-runMode:beforeDate:
方法"运行一次循环,在指定的模式下阻止输入,直到给定的日期。"还有一种-run
方法,它"将接收器置于永久循环中,在此期间它处理来自所有附加输入源的数据。"
重复调用运行循环(或者调用-run
,听起来像是它自己做的)怎么可能不消耗CPU?当主运行循环运行时,Cocoa应用程序可以在后台空闲,并且它将消耗零(或几乎零)CPU时间。
在-runMode:beforeDate:
中,如何在不轮询和消耗CPU的情况下运行循环块,直到接收到输入或定时器启动?
这是一个很好的借口来窥探一些Mac OS X内部!幸运的是,Core Foundation的相关部分都是开源的。
"一个程序如何在不占用CPU的情况下等待X?"通常的答案是"内核做到了。"在这种情况下,运行运行循环实际上只是告诉内核你在等待什么,然后让内核上下文切换。在这种情况下,运行循环的大部分时间都花在带有标志MACH_RCV_MSG
的mach_msg
中,这实际上只是对内核的系统调用,最终会调度其他线程运行。最终,发生了一些有趣的事情,这意味着Mach消息被发送到Mach端口,内核唤醒被阻塞的线程并传递消息。程序将此视为mach_msg
函数的返回。
有各种各样的隐蔽方式可以让马赫信息发送给你。例如,如果您设置了一个NSTimer
,它可能会一直工作到mk_timer_arm
系统调用,这只会导致在一定时间后将Mach消息发送到某个地方。因此,运行循环与其说是一个奇特的无限循环,不如说是内核和Cocoa(或Core Foundation)框架之间的调度器和映射。
不用说,如果内核因为您在等待消息而暂停了您的线程,那么您实际上没有使用任何CPU时间,这就是为什么您的应用程序看起来是空闲的。既然你有一个现代内核,为什么还要麻烦无限循环呢?