Run loop

我们来问一个很简单的问题:我们所写过的程式,大多都是从头到尾、一行一行往下执行,执行完毕,程式就结束;那么,一个GUI 应用程式—无论是我们现在正在学习的iOS 与Mac OS X、还是其他平台—为什么不是打开之后一路执行到底结束,而是会停留在屏幕中等待我们操作?

原因很简单,因为一个GUI 应用程式开始执行之后,就会不断执行一个loop,直到用户决定要关闭这个应用程序的时候,才会关闭这个loop。这样的回圈在Windows 平台上叫做message loop,在iOS 与Mac OS X 上叫做run loop,而这个回圈当中所做的,就是收取与分派事件。

每一轮run loop 的时间并不固定,会与这一轮run loop 里头做了多少事情相关,比方说,如果我们的画面复杂,在App 中同时有很多view,那么这一轮runloop 就得要花上比较多的时间寻找first responder;而像我们在UI 上放了一个按钮,然后按钮按下去要做一些事情,全都会算入到这一轮run loop 的时间。如果我们的程式做了一件很花时间的事情,让这一轮runloop 执行非常久,就会导致「应用程式介面没有回应」这种状态,当介面卡住一段时间,应用程式就会被系统强制关闭。

Timer也是倚靠run loop运作的。当我们建立了一个NSTimer实例之后,下一步就是要把timer实例注册到run loop当中,如果只建立了NSTimer物件,像是只调用了allocinit,这个timer并不会有作用,而调用scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:会在建立NSTimer实例之外,同时将timer加入到run loop中。

Timer 运作的原理是,在每一轮run loop 里头,会检查是否已经到了某个timer 所指定的时间,如果到了,就执行timer 所指定的selector。所以我们可以知道几件事:

  1. 由于每一轮runloop 的时间不一定,所以我们其实也不能够期待timer 会在非常精确的时间执行。 前一轮runloop 如果做了很花时间的事情,就会影响到原本应该要执行的timer 实际执行的时间。
  2. 虽然并没有所谓的最小的时间单位这件事情,但是timer 的时间间隔一定会有一个上限,我们不可能建立比run loop 的频率还要更频繁的timer。

我们在讲selector 与内存管理的时候也提到,runloop 的其中一项功能还包含管理auto-release 实例。Mac OS X 与iOS 早期并没有自动化的内存管理,当时会使用一套叫做auto-release 的半自动机制方便管理记忆体,在每一轮run loop 中,如果某些实例只有在这一轮run loop 中有用,之后就应该释放,我们可以先把实例放进auto-release pool 里头,等到这一轮run loop 的时候,再把auto-release pool 倒空。

讲到这里,我们可以来谈iOS与Mac OS X的程序进入点到底在哪里。我们在写第一个iOS App的时候,可能第一个改写的地方是-application:didFinishLaunchingWithOptions:,就以为这个method是iOS App的程式进入点。但iOS App的程式进入点其实就跟所有的C语言程式一样,是main()。我们来看main.m里头写了什么。

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

在进入-application:didFinishLaunchingWithOptions:之前,会

  1. 建立auto-release pool
  2. 调用UIApplicationMain,而这个function 会
  3. 建立UIApplication 这个singleton 实例
  4. 开始执行run loop
  5. 这些步骤完毕后,代表app 已经开始执行,所以
  6. 对UIApplication 的delegate 调用 -application:didFinishLaunchingWithOptions:

在Cocoa 与Cocoa Touch 应用程式中,我们会使用CFRunloop 与NSRunloop 等实例,描述runloop。

results matching ""

    No results matching ""