内容简介:强烈推荐关于
强烈推荐 ibireme
大神的文章深入理解RunLoop
关于 Runloop
,尽管早就知道它的本质实现是一个循环,但笔者还是一直很困惑它的作用是什么 ,不过最近整理相关知识总算是理解了。
代码的执行逻辑是自上而下的,如果没有 Runloop
,代码执行完毕后,程序就退出了,对应到实际场景就是 APP
一打开立马就退出了。
int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"程序执行中..."); } return 0; } // log 程序执行中... Program ended with exit code: 0 复制代码
例如上面的代码,代码执行完毕后, main
函数返回,然后程序退出。
为什么工作中,好像没有编写 Runloop
相关的代码,程序还是能够稳定持续运行呢?
int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } 复制代码
这是因为程序自动帮我们在 UIApplicationMain…
中做了这个事情。
下面来看看 Runloop
的简化的伪代码,主要来自sunnyxx 大神的一次视频分享:
function loop() { do { 有事干了 = 我睡觉了没事别找我(); if (搬砖) { 搬砖(); } else if (吃饭) { 吃饭(); } } while (活着) } 复制代码
这个伪代码看着还是有一点抽象,需要了解的一个知识点是线程和 RunLoop
之间是一一对应的,这里的睡觉了可以理解为线程休眠 [NSThread sleepUntilDate:...]]
,也就是说当应用没有任何事件触发时,就会停在睡觉那行代码不执行,这样就节约了 CPU
的运算资源,提高程序性能,直到有事件唤醒应用为止。例如上面的搬砖事件,吃饭事件。处理完后,又会进入睡觉状态直到下次唤醒,反复循环,这样就保证了程序能随时处理各种事件并能够稳定运行。
实际上触摸事件、屏幕 UI
刷新、延迟回调等等都是 Runloop
实现的。
Runloop 的结构
先来看看 Runloop
的结构源码:
struct __CFRunLoop { pthread_t _pthread; CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; CFRunLoopModeRef _currentMode; CFMutableSetRef _modes; // ... }; 复制代码
这里包含一个线程的成员变量 _pthread
,可以看出 Runloop
确实和线程是息息相关的。还能看到 Runloop
拥有很多关于 Model
的成员变量,再来看看 Model
的结构:
struct __CFRunLoopMode { CFStringRef _name; CFMutableSetRef _sources0; CFMutableSetRef _sources1; CFMutableArrayRef _observers; CFMutableArrayRef _timers; // ... }; 复制代码
先不管这些东西是干什么的,至少我们现在能够得出如下图所示的理解:
一个 Runloop
中包含若干个 Model
,每个 Mode
又包含若干个 Source/Timer/Observer
。
Runloop 的 Model
Model
代表 Runloop
的运行模式, Runloop
每次只能指定一个 Model
作为 _currentMode
,如果需要切换 Mode
,只能退出当前 Loop
,再重新选择一个 Mode
进入。主线程的 Runloop
这里有两个预置的模式 ,并且这也是系统公开的两个 Model
:
-
kCFRunLoopDefaultMode:
APP
的普通状态,通常主线程是在这个Mode下运行,已被标记为Common
。 -
UITrackingRunLoopMode:
App
追踪触摸ScrollView
滑动时的状态,保证界面滑动时不受其他Mode
影响,已被标记为Common
。
注意 Runloop
的结构中有一个 _commonModes
。这里是因为一个 Mode
可以将自己标记为 Common
(通过将其 ModeName
添加到 RunLoop
的 commonModes
中 ),标记为 Common
的 Model
都可以处理事件,可以理解为变相的实现了多个 Model
同时运行。同时系统也提供了一个操作 Common
标记的字符串-> kCFRunLoopCommonModes
。如果我们想要上面两种模式下都能处理事件,就可以使用这个字符串。
Model 中的 Item
Source/Timer/Observer
被统称为 mode item ,不同 Model
的 Source0/Source1/Timer/Observer
被分隔开来,互不影响,如果 Mode
里没有任何 Source0/Source1/Timer/Observer
, RunLoop
会立马退出。
Source
Source
是事件产生的的地方,它对应的类为 CFRunLoopSourceRef
。 Source
有两个版本: Source0
和 Source1
。
-
Source0
只包含了一个回调(函数指针),它并不能主动触发事件。 -
Source1
包含了一个mach_port
和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种Source
能主动唤醒RunLoop
的线程。例如屏幕触摸、锁屏和摇晃等。
Timer
Timer
对应的类是 CFRunLoopTimerRef
,它其实就是 NSTimer
,当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
Observer
Observer
对应的类是 CFRunLoopObserverRef
,当 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 }; 复制代码
Runloop 的内部逻辑
打开开头的 Runloop
的源码,面对众多代码,让人毫无头绪,但是前文中已经讲到,屏幕的触摸事件是 Runloop
来处理的。于是打个断点,来查看程序的函数调用栈:
从图中能看到, Runloop
是从 11
开始的,于是从源码中搜索 CFRunLoopRunSpecific
函数,这里只探究内部主要逻辑,其他细节不看,下面是 精简后 的函数:
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ // 根据 modeName 获取currentMode CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false); // 设置 Runloop 的 Model CFRunLoopModeRef previousMode = rl->_currentMode; rl->_currentMode = currentMode; // 通知 Observers: 即将进入 RunLoop __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); // 进入 runloop result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); // 通知 Observers: RunLoop 即将退出 __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); return result; } 复制代码
然后再进入 __CFRunLoopRun(...)
函数查看内部精简后的主要逻辑源码:
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { int32_t retVal = 0; do { // 通知 Observers: 即将处理 Timers __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); // 通知 Observers: 即将处理 Sources __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); // 处理 Blocks __CFRunLoopDoBlocks(rl, rlm); // 处理 Sources0 if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) { // 处理 Blocks __CFRunLoopDoBlocks(rl, rlm); } // 判断有无 Sources1 if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) { // 跳转到 handle_msg 处理 Sources1soso goto handle_msg; } // 通知 Observers: 即将休眠 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); // 开始休眠 __CFRunLoopSetSleeping(rl); // 等待消息唤醒当前线程 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY); // 结束休眠 __CFRunLoopUnsetSleeping(rl); // 通知 Observers: 结束休眠 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); // 处理 handle_msg:; // 被 timer 唤醒 if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { // 处理 timer __CFRunLoopDoTimers(rl, rlm, mach_absolute_time()) } // 被 gcd 唤醒 else if (livePort == dispatchPort) { // 处理 gcd __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); // 被source1唤醒 } else { __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply); } // 处理 Blocks __CFRunLoopDoBlocks(rl, rlm); // 设置返回值 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; } } while (0 == retVal); return retVal; } 复制代码
可以看到 Runloop
内部确实是一个循环,并且,唤醒 RunLoop
的方式有 mach port
、 Timer
和 dispatch
。笔者最初在疑惑一个问题,上面的函数调用栈是一个点击屏幕后的响应事件,可以看出这里是 sources0
,明明是一个触摸事件为什么不是 sources1
呢,笔者 猜测 sources1
这里唤醒了 Runloop
,因为 sources0
是无法唤醒 runloop
的,然后再在 sources0
的回调中处理的点击事件。
RunLoop 中的 mach port
这里由于目前笔者水平有限,只能够理解到 mach port
是一个可以控制硬件和接受硬件反馈的一个系统,然后可以通过它将来自硬件的操作转化成熟知的 UIEvent
事件等等。
总结
这篇文章主要讲解了 Runloop
到底是一个什么东西,当然 Runloop
的知识不仅仅只有这篇文章这点。例如实际用处中的线程保活(AFNetworking 2.x 版本中),滑动时 Timer
怎么不被停止,自动释放池的实现等等都用到了 Runloop
。
以上所述就是小编给大家介绍的《iOS 浅谈 Runloop》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Two Scoops of Django
Daniel Greenfeld、Audrey M. Roy / CreateSpace Independent Publishing Platform / 2013-4-16 / USD 29.95
Two Scoops of Django: Best Practices For Django 1.5 is chock-full of material that will help you with your Django projects. We'll introduce you to various tips, tricks, patterns, code snippets, and......一起来看看 《Two Scoops of Django》 这本书的介绍吧!