iOS Runloop

来源:互联网 发布:js attr方法 编辑:程序博客网 时间:2024/05/22 16:00

什么是Runloop

在iOS开发中,NSObject对象的两个方法performSelector:withObject:performSelector:withObject:afterDelay
问后者afterDelay参数传0与前者有何异同?
要回答这个问题,就不得不提起Runloop。

在没有Runloop机制的设计里,程序的流程是启动->执行任务->结束,而App为了持续捕获用户的输入、保持应用运行,应用了Runloop机制处理应用任务。典型的Runloop结构如下:

int main(int argc, char * argv[]) {    while(AppIsRunning) {        id whoWakesMe = SleepForWakingUp();        id event = GetEvent(whoWakesMe);        HandleEvent(event);    }    return 0;}

其中 while 循环保证了线程在处理完事件之后不会退出;
SleepForWakingUp 函数,让线程在没有事件需要处理的时候陷入休眠,让出 CPU;当事件到来时,线程被激活,whoWakesMe 获得返回值,代码从原来的休眠处重新跑起来,执行余下的操作。

在iOS开发中,Apple为我们提供了CFRunLoopRef以及NSRunLoop操作Runloop。
在应用的主线程,系统已经准备好了Runloop,我们平时使用的很多类库,都是依赖于Runloop的:

NSTimer 定时器基于Runloop计时
Autorelease 每次loop结束后释放autorelease对象
GCD dispatch_after都是基于Runloop的
performSelector:AfterDelay: 一类NSObject的方法

RunloopMode

RunloopMode 是一个要监控的Input和Timer事件源的集合或者是一个要通知的Runloop观察者的集合。每次运行Runloop,都要指定一个运行模式。在Runloop的运行期间,只有和当前运行模式相关的源才能被监控和允许发送事件。和其他模式相关的源会保留,直到Runloop运行在了合适的模式才会分发。
RunloopMode的这个特性是iOS顺滑的秘诀,例如在webView滑动时,webView的内容会停止加载,直到停止滑动。这也是开发者进行性能优化的一大入口。有以下几种 mode:

NSDefaultRunLoopMode:默认的状态,也是空闲的状态——对 APP > 没有操作时,main run loop 就会处于这个 mode;
UITrackingRunLoopMode:滑动 ScrollView 时会切换到这个 mode;
UIInitializationRunLoopMode:私有,在 APP 启动时会处于这个mode,启动后 切到 default;
NSRunLoopCommonModes:默认情况下包含 1 与 2 两种 mode;

Input Sources

是Runloop的数据源抽象类,包括两种version 的source

  1. source0,处理 APP 内部事件,APP 自己负责管理(触发),例如 touch 事件,UIEvent,CFSocket。source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
  2. source1,由 run loop 和内核管理,Mach port 驱动,例如 CFMachPort,CFMessagePort。关于 port:给某个进程发消息可以发到某个 port 上,如果进程监听这个 port,就可以收到这个消息。注意,是进程。一个 app 就是一个进程。
    sources

Timer Sources

Timer Sources同步地在将来的一个确定的时间分发事件到我们的线程。Timers可以让线程通知自己去处理一些事情。Timers不是一个实时的机制,当Timers触发的时候NSrunloop刚好正在执行处理函数,Timers会等待NSRunloop调用自己的处理函数。
Timers可以创建一次性的和重复性的事件,当创建重复性的事件的时候,Timers只会根据规划好的触发时间来重新规划触发时间,而不是根据确切的触发时间。而且由于延迟触发丢失了几次触发的话,Timers只会补充一次触发。
performSelector:afterDelay: 里面使用的也是Timer,文章开头的问题,答案就在此。

NSRunloop Observer

不像是 Input Sources 一样在事件触发的时候执行处理函数。NSRunloop观察者是在NSRunloop几个执行的特定的点触发。NSRunloop可以观察的几个事件是:

进入NSRunloop
NSRunloop将要处理Timer事件
NSRunloop将要处理Input事件
NSRunloop将要进入睡眠
NSRunloop被唤醒,但是是在处理事件之前
退出NSRunloop

创建观察者的方法是CFRunLoopObserverRef,我们可以通过Core Foundation方法添加到指定的NSRunloop。观察者也可以创建一次性的和重复性的。一次性的观察者触发之后就会从NSRunloop中删除。

NSRunloop事件处理顺序

NSRunloop所有处理事件和通知观察者的顺序如下:

  1. 通知观察者NSRunloop进入
  2. 如果有Timer即将触发时,通知观察者
  3. 如果有非Port的Input Sourc即将e触发时,通知观察者
  4. 触发非Port的Input Source事件源
  5. 如果基于Port的Input Source事件源即将触发时,立即处理该事件,跳转到步骤9
  6. 通知观察者当前线程将进入休眠状态
  7. 将线程进入休眠状态直到有以下事件发生:基于Port的Input Source被触发、Timer被触发、Run Loop运行时间到了过期时间、Run Loop被唤起
  8. 通知观察者线程将要被唤醒
  9. 处理被触发的事件:如果是用户自定义的Timer,处理Timer事件后重新启动Run Loop进入步骤2、如果线程被唤醒又没有到过期时间,则进入步骤2、如果是其他Input Source事件源有事件发生,直接处理这个事件
  10. 到达此步骤说明Run Loop运行时间到期,或者是非Timer的Input Source事件被处理后,Run Loop将要退出,退出前通知观察者线程已退出

什么时候使用

主线程的NSRunloop自动运行,所以只有其他线程才需要我们自己运行NSRunloop。以下是几种场景:

  1. 需要Timer的操作。如果不准备Runloop,则timer无法执行。
  2. 子线程中使用performSelector:afterDelay:延迟执行。
  3. 需要让线程执行周期性的工作。
  4. 依赖Mode的操作,例如在scrollView滚动时不加载图片。
0 0
原创粉丝点击