深入理解RunLoop
Mac&iOS之多线程
CFRunLoop运用
RunLoop 的概念
- runloop是一个对象,管理需要处理的事件和消息
NSRunloop是CGRumLoopRef的封装,提供面向对象API - 不可直接创建runloop,只能用CFRunLoopGetMain() 和 CFRunLoopGetCurrent()获取
Runloop 与线程关系
- 线程和RunLoop一一对应,只能在一线程中获取对应的runloop
RunLoop 对外的接口
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
一个Runloop包含若干个mode
mode里有若干个Source,Timer, Observer
CFRunLoopSourceRef 事件产生的地方
source0(不能主动触发事件)
source1,包含一个match_port和一个回调(函数指针)能主动唤醒runloop
CFRunLoopTimerRef
基于时间的触发器,包括时间长度和一个回调(函数指针)CFRunLoopObserverRef
每个Observe都包含一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化(还是状态发生变化就回调?)
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
// 即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1),
// 即将处理 Timer
kCFRunLoopBeforeSources = (1UL << 2),
// 即将处理 Source
kCFRunLoopBeforeWaiting = (1UL << 5),
// 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6),
// 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7),
// 即将退出Loop
};
上面的Source,Timer, Observer统称为mode item,一个item可以同时加入多个mode,如果一个mode中一个item都没有 将会退出runloop
有待考证
RunLoop 的 Mode
CFRunLoopMode 和 CFRunLoop的结构大致如下
struct __CFRunLoopMode {
CFStringRef _name;
// Mode Name, 例如 @“kCFRunLoopDefaultMode"和
UITrackingRunLoopMode
CFMutableSetRef _sources0;
// Set
CFMutableSetRef _sources1;
// Set
CFMutableArrayRef _observers;
// Array
CFMutableArrayRef _timers;
// Array
...
};
struct __CFRunLoop {
CFMutableSetRef _commonModes;
// Set 被标记为“common”属性的mode讲添加到这里
CFMutableSetRef _commonModeItems;
// Set 每次进入 runlOOP 将这些item里(source,timer,observer)的内容同步到标记为“common”标记的mode里
CFRunLoopModeRef _currentMode;
// Current Runloop Mode
CFMutableSetRef _modes;
// Set
...
};
_commonModes:一个mode可以被添加到这里,每次runloop发生变化时,都会讲_commonModeItems(source,timer,observer)的内容同步到这些mode里
应用场景举例:主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。这两个 Mode 都已经被标记为"Common"属性。DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。
有时你需要一个 Timer,在两个 Mode 中都能得到回调,一种办法就是将这个 Timer 分别加入这两个 Mode。还有一种方式,就是将 Timer 加入到顶层的 RunLoop 的 "commonModeItems" 中。"commonModeItems" 被 RunLoop 自动更新到所有具有"Common"属性的 Mode 里去。
RunLoop 的内部逻辑##
实际上 RunLoop 就是这样一个函数,其内部是一个 do-while 循环。当你调用 CFRunLoopRun() 时,线程就会一直停留在这个循环里;直到超时或被手动停止,该函数才会返回。