OC面试相关ios底层原理大前端开发

Runloop原理(一)

2022-06-21  本文已影响0人  erlich

此文章的意图:当你完全细心阅读之后,对runloop认知,会成为你作为一名ios开发人员潜意识里的一部分

一、官方一张图开始

image.png

官方文档开宗介绍

这几句就够了,回答了哲学三问 WDP:What -> runloop是什么?Do -> runloop干嘛用?Purpose -> runloop目的?

二、虽然官方英文描述很晦涩,但是为了准确,还是对官方说明做个解释

(一) runloop 描述

(二)接下来 官方剖析了你如何为你的应用配置runloop

1)意识形态

2)Run Loop Modes

runloop Mode,可以通俗的来讲两个集合,就是要监视的对象集合 和 要通知的对象集合

每次运行runloop,指定一个mode或使用默认mode, 这样只有与指定mode相关联的 sources(存在两种source)会被监听,同样与mode相关联的observers会被通知

runloop 的几种mode

3)Input Sources

Input Sources 往线程交付异步事件,分为两种

在任何时刻,Modes都会影响 Input sources

通常情况下,runloop在 default mode下run,也可以指定 自定义modes

如果Input sources不处于当前关联mode,则它生成的任何event都将保持,直到Input sources处于关联的mode

4)Timer Sources

Timer Sources 在未来一个预设的时间同步地向你的线程交付事件

虽然Timer Sources 产生了一个基于时间的通知,但这个timer并不是一个实时机制

如果Timer sources 不处于当前监视的关联mode,则timer不会被触发

如果timer触发时,runloop正在执行handler处理,则timer自己的handler处理将等待下一次time到来执行

如果runloop没run起来,则timer不会被触发

timer根据计划的时间间隔重新调度自己,并不根据实际触发时间,即使触发时间比计划延时了

5)Run Loop Observers

Sources VS Runloop Observers

你可以使用runloop Observers准备你的线程来处理给定的事件

你也可以在线程进入休眠之前准备线程

你可以将runloop Observers与以下事件关联

你可以通过 Core Foundation 添加 runloop Observers,可以根据自己感兴趣的事件,设置自定义回调 和 活动

创建一个runloop Observer时,你可以指定 是一次性 还是 重复的(once or repeatedly)

- once observer在触发后,将自身从runloop中移除

- repeatedly observer在触发后,仍然附加在runloop中

6)runloop事件序列

事件序列:

  1. Notify observers that the run loop has been entered.

    通知observer 已经进入runloop

  2. Notify observers that any ready timers are about to fire.

    通知observer 即将处理timer

  3. Notify observers that any input sources that are not port based are about to fire.

    通知observer 即将处理非基于port的 input source

  4. Fire any non-port-based input sources that are ready to fire.

    通知observer 处理 非基于port的 input source

  5. If a port-based input source is ready and waiting to fire, process the event immediately. Go to step 9.

    如果基于port的 input source 已ready,等待触发,则立即处理事件。执行步骤9

  6. Notify observers that the thread is about to sleep.

    通知observer 线程即将休眠

  7. Put the thread to sleep until one of the following events occurs:

    线程休眠 直到以下几个事件之一发生

    • An event arrives for a port-based input source.

      基于port的 input source事件到来

    • A timer fires.

      timer 触发

    • The timeout value set for the run loop expires.

      runloop 设置的超时 过期

    • The run loop is explicitly woken up.

      runloop 被显式唤醒

  8. Notify observers that the thread just woke up.

    通知observer 线程被唤醒

  9. Process the pending event.

    处理挂起的事件

    • If a user-defined timer fired, process the timer event and restart the loop. Go to step 2.

      如果用户定义的timer触发,处理timer事件,并重启runloop 跳转2

    • If an input source fired, deliver the event.

      如果input source触发,交付事件

    • If the run loop was explicitly woken up but has not yet timed out, restart the loop. Go to step 2.

      如果runloop被显式唤醒,但还没有超时,则重启runloop 跳转2

  10. Notify observers that the run loop has exited.

通知observer 退出runloop

由于timer和input sources的observer通知 在事件发生之前,所以通知和事件实际发生有时间缝隙

可以用sleep 和 awake-from-sleep 通知来帮助关联实际事件之间的间隔

由于timer和其他周期性事件是在runloop run时交付的,因此绕过该循环将中断这些事件的交付

7)何时使用runloop

主线程runloop自动启动,你不需要主动调用run

子线程

8)创建一个runloop observer

image.png
image.png

CFRunLoopObserverContext 结构体


image.png

好了代码有了,不妨做个测试,当下我用的M1电脑 模拟器

image.png

此时,下面的控制台是没有任何额外输出的,也就是 打印停在了44次

我不做任何操作 ,控制台依旧是安静的

这个时候 我从键盘上随便按下一个键 (注意:此时模拟器应该在前台) 控制台打印追加到了 observer 回调 60次, 较上一次,增加了16次,记住这个差值16

接下来控制太依旧安静下来

控制台安安静静,而且模拟器什么也不做,也不触发什么操作,这不就是线程休眠

我们按下任意键,控制台接着打印,这不就是线程唤醒么,观察者收到了runloop的通知,16次通知,具体每次信息,我们没做打印,暂且按下不表,继续往后分析

根据初步测试runloop,通过Core Foundation,我们在主线程注册了一个 runloop 观察者,设置了observer 回调函数,成功接收到了 runloop的通知

起码 我们查探runloop 的方向是确立了,runloop给了一定的响应通知信息

(1)主线程Runloop Observer通知信息

由于我的M1 xcode一查看堆栈就崩溃,所以改用我的x86 mac,打印信息有出入的地方,相信你们可以忽略掉

以下为boserver每次通知堆栈信息,以下是严格按照顺序的,请耐心

image.png image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png

..... kCFRunLoopoBeforeTimers

..... kCFRunLoopBeforeSources

image.png

。。。。。。接下来线程休眠

image.png image.png

activity -

通过主线程注册observer,我们得到了一个runloop的序列活动流程

image.png

你会发现 [runloop run] , kCRunLoopExit 之后,马上又 kCFRunLoopEntry,也就是runloop进入之后,基本上不会退出了,因为退出之后 马上又entry了,感兴趣可以自己测试体验下
这个 [runloop run]

(2)run vs runUntilDate

上面的测试中使用runloop run,线程休眠后, 点击屏幕 控制台是没有打印的,也就是touch事件并没有唤醒线程

真的是这样吗?

此时模拟器是黑的,view还未正常load出来呀,我们验证下

我们在threadMain 方法结束之前添加 一句打印

image.png

此时我们发现 [runloop run] 后面的打印并未在控制台打印出来,说明 [runloop run] 直接阻塞了后面代码的执行

改用 runloop runUntilDate:

image.png

有些不一样了

我们添加的打印正常执行了 这时并没有阻塞后面代码的执行 窗口不是黑背景了 说明view正常加载了

我们还发现 打印语句之前,最后一次打印的activity 为0x80, 正是 kCFRunLoopExit,也就是runloop退出了,所以后面的打印才可能正常执行

(3)给 runloop runUntilDate 循环多次看看

image.png

发现第一次runloop runUntilDate之后,runloop 退出,再次执行 runloop runUntilDate,再次exit

原来runloop可以这样操作,这些细节 其实是了解runloop的关键,因为有些摸不着头脑的东西 不仔细揣摩这些细节 是没办法get到的

(4)创建一个timer

image.png

加个timer之后,你就会发现,不需要再按键或touch,控制台observer通知回调会自动打印,也就是说 timer会不停唤醒线程

(5)配置长的声明周期线程

为一个长的声明周期线程配置runloop时,最好至少添加一个Input Source 来接收消息

尽管您可以只附加一个timer进入运行循环,但一旦timer触发,它通常会失效,这将导致runloop退出

附加一个重复timer可以使runloop运行更长的时间,但是需要定期触发timer来唤醒线程,这实际上是轮询的另一种形式

相比之下,Input Source等待事件发生,在事件发生之前保持线程睡眠

Runloop原理(二)

上一篇下一篇

猜你喜欢

热点阅读