初识iOS RunLoop
iOS中应用程序app从开始运行一直处于等待的状态,接收到类似点击事件交互后执行相应的操作,完成后继续等待状态,直到我们将程序杀死。那么这个过程是怎么发生的呢?是如何通过相应的方式实现这种机制的呢?通过下面的代码探析:
1 | - (void)loop { |
如上面的代码就可以达到这种机制。可以推测而出app从启动开始创建的主线程一定是在一个死循环中,没有任务的时候进行休眠,接收到任务后被激活执行任务。我们可以理解,这样一个管理线程执行任务就是RunLoop机制,线程在执行中的休眠与激活就是RunLoop对象进行管理的。
RunLoop与线程
RunLoop既然是用来管理线程的,那么他们直接有着怎样的关系。实际上,每一个线程中都有一个RunLoop对象,可以通过具体的方法获得。需要注意的是,除了主线程中RunLoop是默认创建并运行激活的,但并不是每一个线程中都有这个实例对象,如果我们不主动获取runloop,这个runloop就不存在,如果我们去获取不存在就去创建并返回。可以通过[NSRunLoop currentRunLoop]方法获取当前线程的runloop。
Cocoa中RunLoop
NSRunLoop是Cocoa框架中runloop的类非线程安全,关于NSRunLoop的方法如下:
1 | + (NSRunLoop *)currentRunLoop;//获取当前线程的runloop,没有则创建并返回 |
1 | - (void)addTimer:(NSTimer *)timer forMode:(NSString *)mode;//将定时器添加到runloop中 |
1 | - (void)run; //开始运行 |
注意上面提供的方法:输入源被注册进Runloop中时会有方法进行remove,但是定时器却没有,但是定时器中的invalidate方法可以将其从runloop中移除,正如官方文档的说明:invalidate是重要也是唯一的可以将定时器从runloop的注销的方法
,所以如果我们创建了定时器,就一定要在不使用时调用invalidate方法。
CoreFoundation中RunLoop
CFRunLoopRef是CoreFoundation框架中runloop的类线程安全,关于CoreFoundation中关于runloop的有一下类:
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
CFRunLoopModeRef
一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响
CFRunLoopSourceRef事件产生的地方。分为Source0 和 Source1
- Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
- Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程
CFRunLoopTimerRef基于时间的触发器
当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
CFRunLoopObserverRef
是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化:
1 | /* Run Loop Observer Activities */ |
CFRunLoopModeRef
苹果公开提供的 Mode 有两个:kCFRunLoopDefaultMode (NSDefaultRunLoopMode)默认情况 和 UITrackingRunLoopMode追踪 ScrollView 滑动时的状态,你可以用这两个 Mode Name 来操作其对应的 Mode。
同时苹果还提供了一个操作 Common 标记的字符串:kCFRunLoopCommonModes (NSRunLoopCommonModes),你可以用这个字符串来操作 Common Item。需要注意的是一个 Mode 可以将自己标记为”Common”属性(通过将其 ModeName 添加到 RunLoop 的 “commonModes” 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。
关于Mode更多点击这里
RunLoop 的内部逻辑
具体的流程如下图:
关于其中的逻辑讲解见参考博文中内容。