乐趣区

iOS知识梳理-runloop

iOS 知识梳理 – runloop

runloop 其实是个很普遍的东西,基本上是个应用框架都有类似的东西,比如 js 或 flutter 里的 event loop,Android 的 looper。

下面我们从最简单的例子来看一下,runloop 的本质

溯源

最初学编程的时候,有个经典题目是用 c /c++ 实现四则运算。大体上要下面这个可以一直输入的效果:

代码大概像这样:

int main()
{
    string str;
    double result;// 保存结果
    while(getline(cin,str))
    {
    // calculate
    cout << "Result is:" << result << endl;
    }
}

由于可以一直输入新的算式,我们自然地使用了一个 while 循环来处理输入,有新的一行输入就去计算结果并输出,没有新的输入时其实就是阻塞等待。

这其实就是最简单的 runloop。

实际应用程序中,无非就是输入的形式更多了一些,执行的任务更复杂了一些,基本框架,其实还是一个 while 循环。

网上看到一个伪代码挺清晰的:

function loop() {initialize();
  do {var message = get_next_message();
      process_message(message);
  } while (message != quit);
}

深入理解 runloop

runloop 的实际实现还是有大量细节的,core foundation 框架也是开源的,这里其实可以挖得很深。

iOS 多线程:『RunLoop』详尽总结,这篇文章讲得挺好,推荐阅读。很多细节本文就不再做太多展开了。

Mode

runloop 在运行时有个 mode 的概念,runloop 只会处理当前 mode 的 event。

比如,我们有 mode1 和 mode2,mode1 对应了 3 个 event,mode2 对应了 2 个 event,如果当前 runloop 处于 mode1,就只会处理 mode1 对应的 3 个 event。

具体而言,runloop 暴露出来的是 kCFRunLoopDefaultMode 和 UITrackingRunLoopMode 两个 mode,通常主线程处于 DefaultMode,但是当滑动 scrollview 时,主线程处于 TrackingMode。而默认地,我们的 NSTimer 事件是绑定在 DefaultMode 的,这就会导致,当滑动 scrollview 时,就不会触发 NSTimer 事件。

为了解决这种问题,runloop 给出了一个 CommonMode 的概念,CommonMode 不是一个具体的 Mode,可以理解成一组 Mode 的集合,这里实际上就是 DefaultMode 和 TrackingMode。把 Timer 加入到 CommonMode,则在 DefaultMode 和 TrackingMode 都会执行这个 Timer 了。

Mode 这个东西,在实际使用中,除了给 Timer 的使用带来困扰之外,几乎从未被使用过。

那么,为什么苹果会在 Runloop 中设计 Mode 这个东西?

网上并没有找到有价值的讨论,不过可以简单揣测一下,目的可能是,在某些场景下,可以只处理对应场景需要的事件。这可能是基于一种特殊情况的假设:在某种场景下,CPU 忙不过来,因此需要停掉其它任务,专注于当前场景的任务。比如,任务 A 需要 90% 的 CPU,其它杂七杂八事件处理需要 40% 的 CPU,那么,停掉那些杂事专注当前场景,可能是有意义的行为。

如果 CPU 并不会跑满的话,那么 Mode 的拆分就意义不大,充其量是副作用极大的优先级调整(低优先级的直接被卡住了诶)。

参考其它平台的 EventLoop 实现,通常只是提供简单的优先级设置能力即可。

总的来说,个人觉得 Mode 的设计是比较鸡肋的。

子线程的 Runloop

默认地,子线程并不会开启 Runloop。想一想,这是非常合理的。开启了 Runloop 后,即使在没有事件时,线程也会进入阻塞等待的状态,需要的时候那叫保活,不需要的时候就是白占资源了。因此,子线程默认情况下是肯定不能开启 Runloop 的。而没有开启 Runloop 的情况下,涉及到 Runloop 的异步调用就都不能用了,主要就是 NSTimer,performSelector:afterDelay:,网络请求的回调等。

解决方案是通过 [[NSRunLoop currentRunLoop] run] 启动当前线程的 runloop。

跟 Runloop 相关的特性

  1. autoreleasepool:每次 runloop 开始时进入 pool,结束时离开 pool。从而在未显式声明 autoreleasepool 的时候释放 autorelease 对象。
  2. 界面更新:操作 UI 时会把对应的 UIView/CALayer 标记为待处理,在每次迭代即将结束时会执行实际的绘制。
退出移动版