新生代农民工的主页

深入理解 iOS RunLoop

字数统计: 2.8k阅读时长: 12 min
2022/02/20

RunLoop 是iOS中重要的一个概念,在 iOS 里它是由 CFRunLoop 实现。本章将从源码的方面梳理下RunLoop相关的概念、结构、原理。

浅谈RunLoop

RunLoop概念:

一般来讲,一个线程在执行任务完成后,就会结束。但是在我们的应用程序中不会直接结束,而是会在线程中构建一个消息循环机制。当有事件要去处理时保活线程,当没有事件要处理时让线程进入休眠,这个消息循环的机制就是RunLoop

RunLoop和线程的关系:

  • RunLoop是基于线程来管理的,所以是一一对应的关系
  • 主线程中会自动获取Runloop,子线程默认不会获取Runloop

RunLoop创建:

苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:CFRunLoopGetMain()CFRunLoopGetCurrent(),这两个函数最终会调用私有函数_CFRunLoopGet0()
相关源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFLock_t loopsLock = CFLockInit;

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}

RunLoop结构

RunLoop的构成由以下几个定义的对象组成:

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopObserverRef
  • CFRunLoopTimerRef
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // CFRunLoopModeRef 
    typedef struct __CFRunLoopMode *CFRunLoopModeRef;
    // CFRunLoopRef
    typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
    // CFRunLoopSourceRef
    typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopSource * CFRunLoopSourceRef;
    // CFRunLoopObserverRef
    typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopObserver * CFRunLoopObserverRef;
    // CFRunLoopTimerRef
    typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;

- CFRunLoopRef

CFRunLoopRef包含了对应的线程,Mode集合,当前的Mode等等;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 结构体定义
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;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};

- CFRunLoopModeRef

CFRunLoopModeRef代表着RunLoop的运行模式,一个RunLoop中可以有多个mode,一个mode里面又可以有多个source、observer、timer等等。系统五个Mode:

  • kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
  • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
  • UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
  • GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
  • kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // 结构体定义
    struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock; /* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    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 */
    };

- CFRunLoopSourceRef

CFRunLoopSourceRef是产生事件的地方。Source有两个版本:Source0 和 Source1。

1
2
3
4
5
6
7
8
9
10
11
12
// 结构体定义
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;
};

Source0于Source1的区别如下:

  • Source0 只包含了一个回调 ,它并不能主动触发事件
  • Source1包含了一个 mach_port和一个回调 ,被用于通过内核和其他线程相互发送消息(系统消息,例如触摸手势,屏幕解锁,按键等等)这种Source 能主动唤醒RunLoop 的线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Source0
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, CFRunLoopMode mode);
void (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
void (*perform)(void *info);
} CFRunLoopSourceContext;

// Source1
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_OSX || 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;

- CFRunLoopObserverRef

CFRunLoopObserverRef即是RunLoop的Observer,在它的结构体内部有一个_runLoop成员和回调,每个Observer只能监听一个RunLoop,当 RunLoop 的状态发生变化时,Observer就会被触发回调。RunLoop的六个状态如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 结构体定义
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 Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 进入 loop
kCFRunLoopBeforeTimers = (1UL << 1), // 触发 Timer 回调
kCFRunLoopBeforeSources = (1UL << 2), // 触发 Source0 回调
kCFRunLoopBeforeWaiting = (1UL << 5), // 等待 mach_port 消息
kCFRunLoopAfterWaiting = (1UL << 6), // 接收 mach_port 消息
kCFRunLoopExit = (1UL << 7), // 退出 loop
kCFRunLoopAllActivities = 0x0FFFFFFFU // loop 所有状态改变
};

- CFRunLoopTimerRef

CFRunLoopTimerRef是基于Timer的触发器,在它的结构体内部有一个时间间隔和回调,一个Timer仅能够添加到一个RunLoop中,当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间间隔到达后,RunLoop会被唤醒以执行该回调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 */
};

以上这些就是对RunLoop结构的介绍,下面我将结合CFRunLoop 的源码 来跟分析下 RunLoop 的原理吧。

RunLoop实现原理

首先,我们熟悉下源码中的主流程函数调用顺序:

CFRunLoopRun() -> CFRunLoopRunSpecific() -> __CFRunLoopRun()

下面是基于源码精简的流程:

1
2
3
4
5
6
7
8
// CFRunLoopRun 
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// CFRunLoopRunSpecific
NSInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;

// 通知 observers 状态发生变化,进入loop
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// RunLoop里面具体要做的事情, 主要是一个循环
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知 observers 状态发生变化,退出loop
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// __CFRunLoopRun
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
uint64_t startTSR = mach_absolute_time();
int32_t retVal = 0;
do {
...

// 通知 Observers RunLoop 状态发生变化 触发 Timer 回调
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知 Observers RunLoop 状态发生变化 触发 Source0 回调
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 接着执行加入的 block回调
__CFRunLoopDoBlocks(rl, rlm);
// 接着触发 Source0 回调
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
// 可能会再次执行加入的 block回调
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks(rl, rlm);
}
// 接着如果有 Source1 是 ready 状态的话,就会跳转到 handle_msg 去处理消息
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}

}

...

//
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);

...

// 等待唤醒
do {
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
// 基于port的source事件,调用者唤醒
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
// Timer 时间到、RunLoop 超时
if (rlm->_timerFired) {
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
break;
}
} while (1);

...
// 接收到 mach_port 消息, RunLoop 的线程被唤醒了
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:;
...
// 接着开始处理消息
// 如果 Timer 时间到,就触发 Timer 回调
if (msg-is-timer) {
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer, because we apparently fired early
__CFArmNextTimerInMode(rlm, rl);
}
}
// 如果 dispatch 就执行 block
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
// Source1 事件的话,就处理这个事件
else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}
...
// 执行加入的 block回调
__CFRunLoopDoBlocks(rl, rlm);

// loop 已经走完,这里要根据RunLoop 的状态来判断是否需要走下一个 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)) {
// mode 为空,RunLoop 结束
retVal = kCFRunLoopRunFinished;
}

} while (0 == retVal);

return retVal;
}

第一步:外层函数执行了do while,为线程后面loop做准备。 通知observers,RunLoop状态变化:kCFRunLoopEntry,RunLoop 要开始进入循环了;

注意: 看这里的while条件(kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result)

第二步:开启一个 do while 来保活线程,通知 Observers,RunLoop状态变化: kCFRunLoopBeforeTimers, 处理 Timer 回调

第三步:通知 Observers,RunLoop状态变化: kCFRunLoopBeforeSources,处理 Source0 回调

第四步:然后执行加入RunLoop的 block回调,接着会触发 Source0 回调,接着可能会再次执行加入的 block回调,如果有 Source1 是 ready 状态的话,就会跳转到 handle_msg 去处理消息代码如下:

第五步:通知 Observers,RunLoop状态变化:kCFRunLoopBeforeWaiting,线程将进入休眠(sleep)状态

第六步:进入休眠后,会等待 mach_port 的消息再次唤醒

第七步:通知 Observer,RunLoop状态变化:kCFRunLoopAfterWaiting,接收到 mach_port 消息,RunLoop 的线程被唤醒了

第八步:RunLoop 被唤醒后开始处理消息了:

  • 如果是 Timer 时间到了,触发 Timer 的回调
  • 如果是 dispatch ,执行 block回调
  • 如果是 source1 事件,处理这个事件回调

第九步:执行加入的 block回调

第十步:根据RunLoop的状态来判断是否需要走下一个 loop,结束循环 或者 回到外层循环进入第二步

Runloop具体流程可以参考以下这个图
RunLoop流程

写在最后

看完了这篇文章,那什么是RunLoop?它是如何处理事务的呢?

RunLoop其实就是一种事件循环机制,通过这个事件循环机制,当有事件要去处理时保活线程,当没有事件要处理时让线程进入休眠;

RunLoop与线程是什么关系?

RunLoop与线程是一对一的关系,一个RunLoop中之对应一个线程,主线程中会自动获取Runloop,子线程默认不会获取Runloop

如何实现一个常驻线程?

创建RunLoop,添加port/source等事件维护RunLoop的事件循环,启动RunLoop (最简单的做法就是创建一个NSTimer,加入到子线程的RunLoop中,然后启动RunLoop)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//创建Source事件
CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
//创建RunLoop,同时向RunLoop的DefaultMode下加入source
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);

while (run) {
@autoreleasepool {
//令当前的RunLoop运行在DefaultMode下
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
}
}
//在某个情况下 静态变量run = NO时,可以保证跳出RunLoop,线程退出
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);

在了解了RunLoop原理之后,越来越多的人都开始从RunLoop下手,来监控卡顿问题。这其实也是因为线程的事件依赖于RunLoop,而通过监听RunLoop的状态,我们就可以发现调用方法是否执行时间过长,从而判断出是否出现了卡顿。

RunLoop源码

CATALOG
  1. 1. 浅谈RunLoop
    1. 1.1. RunLoop概念:
    2. 1.2. RunLoop和线程的关系:
    3. 1.3. RunLoop创建:
    4. 1.4. RunLoop结构
      1. 1.4.1. - CFRunLoopRef
      2. 1.4.2. - CFRunLoopModeRef
      3. 1.4.3. - CFRunLoopSourceRef
      4. 1.4.4. - CFRunLoopObserverRef
      5. 1.4.5. - CFRunLoopTimerRef
    5. 1.5. RunLoop实现原理
  2. 2. 写在最后