RunLoop
RunLoop
RunLoop
涉及的知识比较底层,它隐晦地运行在线程中,虽然很多时候看不到它,但却无时无刻与其打交道。本文将从API调用开始,逐渐深入RunLoop
。
本文将包含如下内容:
- RunLoop相关API例程
- NSRunLoop 和 CFRunLoopRef
- RunLoop与开发使用
1. 常用API示例
- 平台:iOS
- 语言:Objective-C
- 源码地址:源码
例程中包括了Timer
、Source0
、Source1
和Observer
的相关API调用及相关注释,但由于代码有点多,所以这里就不贴出来了。
2. NSRunLoop 和 CFRunLoopRef
2.1 概念
顾名思义,Run Loop
就是运行循环。可以思考下,若一个系统需要处理一个接收用户交互事件的进程,并且要尽量减少资源开销,该怎么做?
首先,因为用户交互事件是不定的,也就是说,为了能让处理的进程接收到事件,就要一直等着,一个for
或者while
是跑不了的。
然后,一个进程不断跑着一个不满足跳出条件的循环是一个很耗系统资源的操作,那么一个休眠唤醒的机制当然也少不了。
所以,Run Loop
就是一个跑在线程中有条件跳出也有休眠唤醒机制的循环。看着挺简单的,但细节却多的很。
Run Loop
剖析图如下:
Run Loop
就是一个在线程thread
中的循环,在循环过程中接收input source
和timer source
两种源并进行处理。
为了满足既要休眠节省系统资源,又要能响应各种源的事件,在Run Loop
中的各个阶段,会响应不同的事件:
typedef enum CFRunLoopActivity : CFOptionFlags {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
} CFRunLoopActivity;
结合Run Loop
、Source
、Timer
、Observer
,Run Loop
中的处理机制如下:
- 通知
Observer
Run Loop
已经启动 - 通知
Observer
任何即将要开始的定时器 - 通知
Observer
任何即将启动的非基于端口的源 - 启动任何准备好的非基于端口的源(non-port-based input sources,
source0
) - 如果基于端口的源(port-based input source,
source1
)准备好并处于等待状态,立即启动,并进入步骤 9。 - 通知观察者线程进入休眠
- 将线程置于休眠直到任一下面的事件发生:
- 某一事件到达基于端口的源(port-based input source,
source1
) - 定时器(
timer
)启动 Run Loop
设置的时间已经超时Run Loop
被显式唤醒
- 某一事件到达基于端口的源(port-based input source,
- 通知观察者线程将被唤醒。
- 处理未处理的事件
- 如果用户定义的定时器(
timer
)启动,处理定时器事件并重启Run Loop
。进入步骤 2 - 如果输入源启动,传递相应的消息
- 如果
Run Loop
被显式唤醒而且时间还没超时,重启Run Loop
。进入步骤 2
- 如果用户定义的定时器(
- 通知
Observer
Run Loop
结束。
用图来表示就是这样:
一个Run Loop
大部分功能的开发思路猜测就是这样的,但仅仅如此的话,后续开发会发现还不够。
在不同的情况下,一个Run Loop
并不希望对所有源进行处理,所以就出现了模式mode
。
mode
可以理解为source
(input source
和timer source
统称为source
,下同)和observer
的集合。在运行一个Run Loop
时,需要指定运行的mode
,只有与这个mode
相关的source
和observer
会被处理。mode
是通过名字来区分的,在开发App时,通常会接触到几个mode
的名字,如下:
// Foundation
NSRunLoopCommonModes
NSDefaultRunLoopMode
UITrackingRunLoopMode
// Core Foundation
kCFRunLoopCommonModes
kCFRunLoopDefaultMode
备注:通常,如上所述,Run Loop
只会处理当前指定mode
中的source
和observer
,但NSRunLoopCommonModes
却很特别,从名字上来看,是个普通mode
,以这个mode
运行的Run Loop
会处理所有添加到common modes
中的mode
中的相关source
和observer
。而从另一个角度看,当一个新mode
被添加到common modes
中时,common modes
中已经添加的那些mode
中的相关source
和observer
也会添加到新的mode
中,并且一旦这个新mode
被添加到common modes
中,就不能被删除了,只能在common modes
运行中的Run Loop
中对source
或observer
进行操作。在App启动时,其内默认有2个mode
,即NSDefaultRunLoopMode
和UITrackingRunLoopMode
,都被添加到common modes
中了,这就是以NSRunLoopCommonModes
运行的RunLoop
能触发UITrackingRunLoopMode
内的相关source
和observer
的原因。原文如下:
Sources, timers, and observers get registered to one or more run loop modes and only run when the run loop is running in one of those modes. Common modes are a set of run loop modes for which you can define a set of sources, timers, and observers that are shared by these modes. Instead of registering a source, for example, to each specific run loop mode, you can register it once to the run loop’s common pseudo-mode and it will be automatically registered in each run loop mode in the common mode set. Likewise, when a mode is added to the set of common modes, any sources, timers, or observers already registered to the common pseudo-mode are added to the newly added common mode.
Once a mode is added to the set of common modes, it cannot be removed.
The Add, Contains, and Remove functions for sources, timers, and observers operate on a run loop’s set of common modes when you use the constant kCFRunLoopCommonModes for the run loop mode.
综上,一个Run Loop
的结构就如下图所示:
2.2 Foundation & Core Foundation
上面讲完了Run Loop
的组成和思路,这里就是代码逻辑分析了。
2.2.1 Run Loop Object
首先,可以观察下NSRunLoop
对象,如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
/*
封装的原点,其实就是通过Core Foundation中的这函数来打印的
static CFStringRef __CFRunLoopCopyDescription(CFTypeRef cf)
*/
NSLog(@"%@", [NSRunLoop currentRunLoop]);
return YES;
}
<CFRunLoop 0x6040001e4e00 [0x108ef8c80]>{
wakeup port = 0x1807,
stopped = false,
ignoreWakeUps = false,
current mode = kCFRunLoopDefaultMode,
common modes = <CFBasicHash 0x6040004468d0 [0x108ef8c80]>{
type = mutable set,
count = 2,
entries =>
0 : <CFString 0x10a268e88 [0x108ef8c80]>{contents = "UITrackingRunLoopMode"}
2 : <CFString 0x108ece818 [0x108ef8c80]>{contents = "kCFRunLoopDefaultMode"}
},
common mode items = <CFBasicHash 0x604000446d50 [0x108ef8c80]>{
type = mutable set,
count = 13,
entries =>
0 : <CFRunLoopSource 0x6040001651c0 [0x108ef8c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10dd9975a)}}
1 : <CFRunLoopSource 0x600000165580 [0x108ef8c80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 16651, subsystem = 0x10a21ffe8, context = 0x0}}
2 : <CFRunLoopObserver 0x600000133f60 [0x108ef8c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x1090d5e01), context = <CFRunLoopObserver context 0x7faab5e01150>}
3 : <CFRunLoopObserver 0x600000133ec0 [0x108ef8c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x1090d5e7c), context = <CFRunLoopObserver context 0x7faab5e01150>}
4 : <CFRunLoopObserver 0x600000133e20 [0x108ef8c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1090a6df2), context = <CFArray 0x60000005c170 [0x108ef8c80]>{type = mutable-small, count = 1, values = (0 : <0x7faab6802048>)}}
5 : <CFRunLoopSource 0x600000166540 [0x108ef8c80]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x604000446ff0, callout = __handleHIDEventFetcherDrain (0x109a03a8e)}}
6 : <CFRunLoopObserver 0x600000134000 [0x108ef8c80]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1090a6df2), context = <CFArray 0x60000005c170 [0x108ef8c80]>{type = mutable-small, count = 1, values = (0 : <0x7faab6802048>)}}
8 : <CFRunLoopSource 0x604000164b00 [0x108ef8c80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 22019, subsystem = 0x10a23a668, context = 0x60400003f7e0}}
9 : <CFRunLoopSource 0x604000166000 [0x108ef8c80]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6040002a1860, callout = FBSSerialQueueRunLoopSourceHandler (0x10d50782f)}}
12 : <CFRunLoopObserver 0x600000133920 [0x108ef8c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10ebcb672), context = <CFRunLoopObserver context 0x0>}
16 : <CFRunLoopSource 0x604000165280 [0x108ef8c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x5103, callout = PurpleEventCallback (0x10dd9bbf7)}}
19 : <CFRunLoopSource 0x600000165700 [0x108ef8c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x604000154b60, callout = __handleEventQueue (0x109a03a82)}}
22 : <CFRunLoopObserver 0x604000133880 [0x108ef8c80]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x10968c57f), context = <CFRunLoopObserver context 0x6040000c63c0>}
},
modes = <CFBasicHash 0x604000446900 [0x108ef8c80]>{
type = mutable set,
count = 4,
entries =>
2 : <CFRunLoopMode 0x604000183190 [0x108ef8c80]>{
name = UITrackingRunLoopMode,
port set = 0x2103,
queue = 0x604000154950,
source = 0x604000183260 (not fired),
timer port = 0x2003,
sources0 = <CFBasicHash 0x604000446cc0 [0x108ef8c80]>{
type = mutable set,
count = 4,
entries =>
0 : <CFRunLoopSource 0x6040001651c0 [0x108ef8c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10dd9975a)}}
1 : <CFRunLoopSource 0x600000166540 [0x108ef8c80]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x604000446ff0, callout = __handleHIDEventFetcherDrain (0x109a03a8e)}}
3 : <CFRunLoopSource 0x604000166000 [0x108ef8c80]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6040002a1860, callout = FBSSerialQueueRunLoopSourceHandler (0x10d50782f)}}
4 : <CFRunLoopSource 0x600000165700 [0x108ef8c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x604000154b60, callout = __handleEventQueue (0x109a03a82)}}
},
sources1 = <CFBasicHash 0x604000446d80 [0x108ef8c80]>{
type = mutable set,
count = 3,
entries =>
0 : <CFRunLoopSource 0x604000165280 [0x108ef8c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x5103, callout = PurpleEventCallback (0x10dd9bbf7)}}
1 : <CFRunLoopSource 0x600000165580 [0x108ef8c80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 16651, subsystem = 0x10a21ffe8, context = 0x0}}
2 : <CFRunLoopSource 0x604000164b00 [0x108ef8c80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 22019, subsystem = 0x10a23a668, context = 0x60400003f7e0}}
},
observers = (
"<CFRunLoopObserver 0x600000134000 [0x108ef8c80]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1090a6df2), context = <CFArray 0x60000005c170 [0x108ef8c80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7faab6802048>\n)}}",
"<CFRunLoopObserver 0x604000133880 [0x108ef8c80]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x10968c57f), context = <CFRunLoopObserver context 0x6040000c63c0>}",
"<CFRunLoopObserver 0x600000133f60 [0x108ef8c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x1090d5e01), context = <CFRunLoopObserver context 0x7faab5e01150>}",
"<CFRunLoopObserver 0x600000133920 [0x108ef8c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10ebcb672), context = <CFRunLoopObserver context 0x0>}",
"<CFRunLoopObserver 0x600000133ec0 [0x108ef8c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x1090d5e7c), context = <CFRunLoopObserver context 0x7faab5e01150>}",
"<CFRunLoopObserver 0x600000133e20 [0x108ef8c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1090a6df2), context = <CFArray 0x60000005c170 [0x108ef8c80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7faab6802048>\n)}}"
),
timers = (null),
currently 548070346 (432207531089863) / soft deadline in: 1.84463119e+10 sec (@ -1) / hard deadline in: 1.84463119e+10 sec (@ -1)
},
3 : <CFRunLoopMode 0x604000183330 [0x108ef8c80]>{
name = GSEventReceiveRunLoopMode,
port set = 0x1d03,
queue = 0x604000154a00,
source = 0x604000183400 (not fired),
timer port = 0x2a03,
sources0 = <CFBasicHash 0x604000446e10 [0x108ef8c80]>{
type = mutable set,
count = 1,
entries =>
0 : <CFRunLoopSource 0x6040001651c0 [0x108ef8c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10dd9975a)}}
},
sources1 = <CFBasicHash 0x604000446e40 [0x108ef8c80]>{
type = mutable set,
count = 1,
entries =>
0 : <CFRunLoopSource 0x604000165340 [0x108ef8c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x5103, callout = PurpleEventCallback (0x10dd9bbf7)}}
},
observers = (null),
timers = (null),
currently 548070346 (432207533164945) / soft deadline in: 1.84463119e+10 sec (@ -1) / hard deadline in: 1.84463119e+10 sec (@ -1)
},
4 : <CFRunLoopMode 0x604000182e50 [0x108ef8c80]>{
name = kCFRunLoopDefaultMode,
port set = 0x1903,
queue = 0x6040001547f0,
source = 0x604000182f20 (not fired),
timer port = 0x2303,
sources0 = <CFBasicHash 0x604000446db0 [0x108ef8c80]>{
type = mutable set,
count = 4,
entries =>
0 : <CFRunLoopSource 0x6040001651c0 [0x108ef8c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10dd9975a)}}
1 : <CFRunLoopSource 0x600000166540 [0x108ef8c80]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x604000446ff0, callout = __handleHIDEventFetcherDrain (0x109a03a8e)}}
3 : <CFRunLoopSource 0x604000166000 [0x108ef8c80]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6040002a1860, callout = FBSSerialQueueRunLoopSourceHandler (0x10d50782f)}}
4 : <CFRunLoopSource 0x600000165700 [0x108ef8c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x604000154b60, callout = __handleEventQueue (0x109a03a82)}}
},
sources1 = <CFBasicHash 0x604000446de0 [0x108ef8c80]>{
type = mutable set,
count = 3,
entries =>
0 : <CFRunLoopSource 0x604000165280 [0x108ef8c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x5103, callout = PurpleEventCallback (0x10dd9bbf7)}}
1 : <CFRunLoopSource 0x600000165580 [0x108ef8c80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 16651, subsystem = 0x10a21ffe8, context = 0x0}}
2 : <CFRunLoopSource 0x604000164b00 [0x108ef8c80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 22019, subsystem = 0x10a23a668, context = 0x60400003f7e0}}
},
observers = (
"<CFRunLoopObserver 0x600000134000 [0x108ef8c80]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1090a6df2), context = <CFArray 0x60000005c170 [0x108ef8c80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7faab6802048>\n)}}",
"<CFRunLoopObserver 0x604000133880 [0x108ef8c80]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x10968c57f), context = <CFRunLoopObserver context 0x6040000c63c0>}",
"<CFRunLoopObserver 0x600000133f60 [0x108ef8c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x1090d5e01), context = <CFRunLoopObserver context 0x7faab5e01150>}",
"<CFRunLoopObserver 0x600000133920 [0x108ef8c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10ebcb672), context = <CFRunLoopObserver context 0x0>}",
"<CFRunLoopObserver 0x600000133ec0 [0x108ef8c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x1090d5e7c), context = <CFRunLoopObserver context 0x7faab5e01150>}",
"<CFRunLoopObserver 0x600000133e20 [0x108ef8c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1090a6df2), context = <CFArray 0x60000005c170 [0x108ef8c80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7faab6802048>\n)}}"
),
timers = <CFArray 0x6000002a06c0 [0x108ef8c80]>{
type = mutable-small,
count = 1,
values = (
0 : <CFRunLoopTimer 0x600000166180 [0x108ef8c80]>{
valid = Yes,
firing = No,
interval = 0,
tolerance = 0,
next fire date = 548070347 (1.23673105 @ 432208772212013),
callout = (Delayed Perform) UIApplication _accessibilitySetUpQuickSpeak (0x107d1a849 / 0x1095971e7) (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/UIKit.framework/UIKit),
context = <CFRunLoopTimer context 0x600000075d80>
}
)
},
currently 548070346 (432207533211792) / soft deadline in: 1.23900005 sec (@ 432208772212013) / hard deadline in: 1.23900002 sec (@ 432208772212013)
},
5 : <CFRunLoopMode 0x600000183b50 [0x108ef8c80]>{
name = kCFRunLoopCommonModes,
port set = 0x360f,
queue = 0x600000154c10,
source = 0x600000183e90 (not fired),
timer port = 0x3f07,
sources0 = (null),
sources1 = (null),
observers = (null),
timers = (null),
currently 548070346 (432207535510991) / soft deadline in: 1.84463119e+10 sec (@ -1) / hard deadline in: 1.84463119e+10 sec (@ -1)
},
}
}
通过这个打印,可以发现如下信息:
- 证实
NSRunLoop
就是CFRunLoop
的封装 - App主线程的
Run Loop
当前mode
是kCFRunLoopDefaultMode
common modes
中添加了2个mode
,UITrackingRunLoopMode
和kCFRunLoopDefaultMode
common mode items
中的是common modes
中所有mode
的相关source
和observer
2.2.2 Core Foundation中的RunLoop
2.2.2.1 Run Loop
结构体
看完了Run Loop
对象,就再来结合结构体理解下对象中的关系:
/* All CF "instances" start with this structure. Never refer to
* these fields directly -- they are for CF's use and may be added
* to or removed or change format without warning. Binary
* compatibility for uses of this struct is not guaranteed from
* release to release.
*/
typedef struct __CFRuntimeBase {
uintptr_t _cfisa;
uint8_t _cfinfo[4];
#if __LP64__
uint32_t _rc;
#endif
} CFRuntimeBase;
typedef struct __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes; // common modes是mode的集合,被包含的mode在kCFRunLoopCommonModes下都生效
CFMutableSetRef _commonModeItems; // common modes中所有mode的source和observer的大杂烩
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name; // mode都是通过名字来区分的
Boolean _stopped;
char _padding[3];
// --- mode下的source和observer集合
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
// ---
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFMutableSetRef _rlModes;
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval; /* immutable */
CFTimeInterval _tolerance; /* mutable */
uint64_t _fireTSR; /* TSR units */
CFIndex _order; /* immutable */
CFRunLoopTimerCallBack _callout; /* immutable */
CFRunLoopTimerContext _context; /* immutable, except invalidation */
};
struct __CFMachPort {
CFRuntimeBase _base;
int32_t _state;
mach_port_t _port; /* immutable */
dispatch_source_t _dsrc; /* protected by _lock */
dispatch_semaphore_t _dsrc_sem; /* protected by _lock */
CFMachPortInvalidationCallBack _icallout; /* protected by _lock */
CFRunLoopSourceRef _source; /* immutable, once created */
CFMachPortCallBack _callout; /* immutable */
CFMachPortContext _context; /* immutable */
CFLock_t _lock;
const void *(*retain)(const void *info); // use these to store the real callbacks
void (*release)(const void *info);
};
// source0的context
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
void (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
void (*perform)(void *info);
} CFRunLoopSourceContext;
// source1的context
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
mach_port_t (*getPort)(void *info);
void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
void * (*getPort)(void *info);
void (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;
typedef struct __CFRunLoopSource * CFRunLoopSourceRef;
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context; // 决定source是source0还是source1
};
typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFIndex _rlCount;
CFOptionFlags _activities; /* immutable */
CFIndex _order; /* immutable */
CFRunLoopObserverCallBack _callout; /* immutable */
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};
Run Loop
对象的打印信息中,包括了这些结构体中的主要信息,再看前面提及的Run Loop
结构图,就很直观了。
2.2.2.2 Run Loop
代码实现
要了解Run Loop
是怎么运行起来的,就要从void CFRunLoopRun(void)
这个函数看起了(由于时跨平台代码,window代码忽略不看就行):
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
// 获得当前线程,并默认以kCFRunLoopDefaultMode模式运行,运行结果用于判断是否跳出
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
/*
Warning: When launching separate processes using the fork function, you must always follow a call to fork with a call to exec or a similar function. Applications that depend on the Core Foundation, Cocoa, or Core Data frameworks (either explicitly or implicitly) must make a subsequent call to an exec function or those frameworks may behave improperly.
*/
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result); // Run Loop跳出条件
}
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
// 若当前Run Loop内存已经释放,直接返回让其结束
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
// 通过mode名从Run Loop中的modes找mode,并设为当前mode;若没有找到,不创建,并直接返回NULL
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
// Run Loop中找不到对应的mode或mode中为空,都直接返回
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
// 因要切换指定的mode,所以先把当前的状态保存下来
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;
// 不同mode下,通过位与操作判断是否允许通知Observer
// 通知Observer触发kCFRunLoopEntry事件,即进入Run Loop
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 运行真正的Run Loop逻辑,并返回运行结果,结果用于判断跳出Run Loop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知Observer触发kCFRunLoopExit事件,即退出Run Loop
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
// 还原原来的mode
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
// 当前时间
uint64_t startTSR = mach_absolute_time();
// 判断是否具备继续运行下去的条件
// 在无介入的情况下,即调用void CFRunLoopStop(CFRunLoopRef rl),默认不会停止运行Run Loop
if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) { // 初始化Run Loop Mode时,默认为false,即不停止mode,除非调用void _CFRunLoopStopMode(CFRunLoopRef rl, CFStringRef modeName)
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
// 若当前为主线程且线程安全,则获取主队列的端口
mach_port_name_t dispatchPort = MACH_PORT_NULL;
Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
// 有DEPLOYMENT_TARGET_MACOSX时,这个宏才有效,即只有macOS会赋值mode的队列端口
#if USE_DISPATCH_SOURCE_FOR_TIMERS
mach_port_name_t modeQueuePort = MACH_PORT_NULL;
if (rlm->_queue) {
modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
if (!modeQueuePort) {
CRASH("Unable to get port for run loop mode queue (%d)", -1);
}
}
#endif
// 设置Run Loop的超时定时器,用于处理Run Loop超时释放
dispatch_source_t timeout_timer = NULL;
struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
if (seconds <= 0.0) { // instant timeout
seconds = 0.0;
timeout_context->termTSR = 0ULL;
} else if (seconds <= TIMER_INTERVAL_LIMIT) {
dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_retain(timeout_timer);
timeout_context->ds = timeout_timer;
timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
dispatch_resume(timeout_timer);
} else { // infinite timeout
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
}
// 进入Run Loop的处理循环
Boolean didDispatchPortLastTime = true;
int32_t retVal = 0;
do {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
voucher_t voucherCopy = NULL;
#endif
uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *msg = NULL;
mach_port_t livePort = MACH_PORT_NULL;
#elif DEPLOYMENT_TARGET_WINDOWS
HANDLE livePort = NULL;
Boolean windowsMessageReceived = false;
#endif
__CFPortSet waitSet = rlm->_portSet;
// 接受唤醒
__CFRunLoopUnsetIgnoreWakeUps(rl);
// 通知Observer触发kCFRunLoopBeforeTimers事件,即处理timer前
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知Observer触发kCFRunLoopBeforeSources事件,即处理source前
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 执行并清空Run Loop中的block,Run Loop上的_blocks_head是个单向链表,其中mode参数是用来判断block是否能执行
__CFRunLoopDoBlocks(rl, rlm);
// 处理source0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
// 基于port的source1通过mach_msg(msg, option, send_size, rcv_size, rcv_name, timeout, notify)函数接收信息
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
#elif DEPLOYMENT_TARGET_WINDOWS
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
goto handle_msg;
}
#endif
}
didDispatchPortLastTime = false;
// 判断是否轮询, 判断条件包含source0是否已经处理完毕,没有处理完且运行通知Observer的话,就会通知Observer触发kCFRunLoopBeforeWaiting事件
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 设置休眠状态
__CFRunLoopSetSleeping(rl);
// do not do any user callouts after this point (after notifying of sleeping)
// Must push the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced.
// 休眠前,把mode中监听的端口也添加到一起,方便管理
__CFPortSetInsert(dispatchPort, waitSet);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
do {
if (kCFUseCollectableAllocator) {
// objc_clear_stack(0);
// <rdar://problem/16393959>
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
// Go ahead and leave the inner loop.
break;
}
} while (1);
#else
if (kCFUseCollectableAllocator) {
// objc_clear_stack(0);
// <rdar://problem/16393959>
memset(msg_buffer, 0, sizeof(msg_buffer));
}
// 接收mode中的端口信息,包括source1(port-based)、timer(_timerPort)、自身超时定时器和调用唤醒函数(_wakeUpPort)
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
#endif
#elif DEPLOYMENT_TARGET_WINDOWS
// Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages.
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
#endif
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
// Must remove the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced. Also, we don't want them left
// in there if this function returns.
// 唤醒后,将休眠前添加的port移除
__CFPortSetRemove(dispatchPort, waitSet);
// 唤醒后,就不需要接受唤醒了
__CFRunLoopSetIgnoreWakeUps(rl);
// user callouts now OK again
// 设置唤醒状态
__CFRunLoopUnsetSleeping(rl);
// 通知Observer触发kCFRunLoopAfterWaiting事件,就是唤醒了
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
// 可以从source1处理那里直接跳到这里进行基于port的信息处理
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
#if DEPLOYMENT_TARGET_WINDOWS
if (windowsMessageReceived) {
// These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
if (rlm->_msgPump) {
rlm->_msgPump();
} else {
MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
// To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced
// Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later.
// NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling.
__CFRunLoopSetSleeping(rl);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
__CFRunLoopUnsetSleeping(rl);
// If we have a new live port then it will be handled below as normal
}
#endif
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// do nothing on Mac OS
#if DEPLOYMENT_TARGET_WINDOWS
// Always reset the wake up port, or risk spinning forever
ResetEvent(rl->_wakeUpPort);
#endif
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer, because we apparently fired early
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
#if USE_MK_TIMER_TOO
// 如果msg来自定时器端口,则处理Timer
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
// On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
// In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
// 如果msg来自主队列的端口,则用主队列处理
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_WINDOWS
void *msg = 0;
#endif
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else { // 若前面的都不是,就可能为source1事件,在这里获取并处理
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD. CFMachPortBoost will look in the TSD for the voucher. By using the value in the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again.
voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);
// Despite the name, this works for windows handles as well
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *reply = NULL;
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
#elif DEPLOYMENT_TARGET_WINDOWS
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
}
// Restore the previous voucher
_CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
// 处理唤醒后触发的
__CFRunLoopDoBlocks(rl, rlm);
// 一些判断循环是否跳出的条件,没有跳出的话,即retVal != 0时,继续执行Run Loop循环
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
voucher_mach_msg_revert(voucherState);
os_release(voucherCopy);
#endif
} while (0 == retVal);
// 释放Run Loop的超时定时器
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}
代码中的逻辑就如前一节概念中的流程一样走了一遍,代码中的关键位置已经做好了注释,其实比较重要的部分是处理过程中穿插observer
通知事件、处理source0
、监听port并接收消息并通过分类(显然,Timer也是通过port接收消息触发的)来进行处理。
3. RunLoop与开发使用
Run Loop
的运行逻辑知道后,能为开发提供些什么?
从前一章提到,Run Loop
的Observer
在kCFAllocatorDefault
模式下,休眠前是会触发kCFRunLoopBeforeWaiting
通知的,而休眠状态,顾名思义,就是App暂时没任务做了,这时候去做一些耗时的处理就是很好的时机了,下面贴出的是IdleRun
项目中的代码,不妨clone
下来跑一下((๑•̀ㅂ•́)و✧)。
代码中,额外添加了一个什么都不做的timer
,目的是让Observer
的按照时间间隔进行触发,并且每次触发时,只加载一个图片。
备注:例子比较简单,只是让一个UICollectionView
加载1280个内部含4个1440x1440相同图片的UImageView
的cell
,没有做任何优化,业务上使用的时候,尤其图片不一样时,需注意离屏渲染、循环引用等内存管理问题。另外,处理做加载,当然也可以利用相似的原理,做更多适合闲时处理的逻辑。
//
// ViewController.m
// IdleRun
//
// Created by lzackx on 2018/3/20.
// Copyright © 2018年 lzackx. All rights reserved.
//
#import "ViewController.h"
typedef void(^TaskBlock)(void);
@interface ViewController () <UICollectionViewDataSource, UICollectionViewDelegate>
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) NSMutableDictionary *tasks;
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation ViewController
- (void)loadView {
UICollectionViewFlowLayout *collectionViewLayout = [[UICollectionViewFlowLayout alloc] init];
collectionViewLayout.itemSize = CGSizeMake(UIScreen.mainScreen.bounds.size.width / 8 - 2, UIScreen.mainScreen.bounds.size.width / 8 - 2);
collectionViewLayout.minimumLineSpacing = 1;
collectionViewLayout.minimumInteritemSpacing = 1;
self.collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds collectionViewLayout:collectionViewLayout];
self.collectionView.backgroundColor = UIColor.whiteColor;
self.collectionView.dataSource = self;
self.collectionView.delegate = self;
self.view = self.collectionView;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"CollectionViewCell"];
[self runRunLoopObserver];
}
// MARK: Run Loop Observer
- (void)runRunLoopObserver {
// The application uses garbage collection, so no autorelease pool is needed.
// Create a run loop observer and attach it to the run loop.
/*
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
} CFRunLoopObserverContext;
*/
CFRunLoopObserverContext context = {
0,
(__bridge void *)(self),
&CFRetain,
&CFRelease,
NULL
};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopBeforeWaiting,
YES,
0,
&observerCallBack,
&context);
if (observer) {
self.tasks = [NSMutableDictionary dictionary];
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.001 repeats:YES block:^(NSTimer * _Nonnull timer) {
// Do Nothing but to tigger observer
}];
CFRunLoopRef cfLoop = CFRunLoopGetCurrent();
CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
CFRelease(observer);
}
}
void observerCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
NSLog(@"%s", __func__);
ViewController *vc = (__bridge ViewController *)info;
for (NSString *key in vc.tasks.allKeys) {
NSLog(@"key: %@", key);
((TaskBlock)vc.tasks[key])();
[vc.tasks removeObjectForKey:key];
break;
}
}
- (void)addTask:(TaskBlock)taskblock forKey:(NSString *)key {
[self.tasks setObject:taskblock forKey:key];
}
// MARK: UICollectionView Data Source
- (nonnull __kindof UICollectionViewCell *)collectionView:(nonnull UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath {
UICollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:@"CollectionViewCell" forIndexPath:indexPath];
cell.backgroundColor = UIColor.lightGrayColor;
[self addTask:^{
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"image"]];
imageView.frame = CGRectMake(0, 0, cell.contentView.bounds.size.width / 2, cell.contentView.bounds.size.height / 2);
[cell.contentView addSubview:imageView];
} forKey:[NSString stringWithFormat: @"%ld.1", (long)indexPath.item]];
[self addTask:^{
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"image"]];
imageView.frame = CGRectMake(cell.contentView.bounds.size.width / 2, 0, cell.contentView.bounds.size.width / 2, cell.contentView.bounds.size.height / 2);
[cell.contentView addSubview:imageView];
} forKey:[NSString stringWithFormat: @"%ld.2", (long)indexPath.item]];
[self addTask:^{
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"image"]];
imageView.frame = CGRectMake(0, cell.contentView.bounds.size.height / 2, cell.contentView.bounds.size.width / 2, cell.contentView.bounds.size.height / 2);
[cell.contentView addSubview:imageView];
} forKey:[NSString stringWithFormat: @"%ld.3", (long)indexPath.item]];
[self addTask:^{
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"image"]];
imageView.frame = CGRectMake(cell.contentView.bounds.size.width / 2, cell.contentView.bounds.size.height / 2, cell.contentView.bounds.size.width / 2, cell.contentView.bounds.size.height / 2);
[cell.contentView addSubview:imageView];
} forKey:[NSString stringWithFormat: @"%ld.4", (long)indexPath.item]];
return cell;
}
- (NSInteger)collectionView:(nonnull UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 1280;
}
@end
总结
Run Loop
如开始所说,涉及Mach
、Core Foundation
等比较底层的知识,隐晦地运行在线程中,虽然很多时候看不到它,但却无时无刻与其打交道,在开发的场景中,我们没法对其进行修改,但是却可以通过熟悉它的运行机制并进行利用。