小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
在苹果的官方文档对Thread的介绍中有提及到runloop,这个图也是官方提供的
这些事务Items所对应的回调分别是:
- block应用:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ - 调用timer:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ - 响应source0:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ - 响应source1:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ - GCD主队列:
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ - observer源:
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
runloop的作用
- 保持程序的持续运行
- 处理App中的各种事件(触摸、定时器、performSelector)
- 节省cpu资源、提高程序的性能,有事的时候就唤醒,没事的时候就休息。
runloop源码查看
runloop和普通的死循环是不一样的。它可以保持程序的持续运行,处理APP中的各种事件(触摸、定时器、performSelector),节省cpu资源、提供程序的性能:该做事就做事,该休息就休息。
runloop的底层是CFRunLoop,我们找到底层源码查看
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.0e10 = 10 ^10 ,这里是对runloop的超时设置,设置一个非常大的值,所以给人的错觉就是一直在运行。
CFRunLoopGetCurrent -> _CFRunLoopGet0
runloop和线程绑定,不论是不是主线程,线程和runloop都是key-value绑定的
// 进行绑定 dict[@"pthread_main_thread_np"] = mainLoop
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
在看下它的数据结构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;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
_commonModes有多个,每一个modes下面可以有多个 _commonModeItem事务。
再来看下CFRunLoopModeRef的结构
struct __CFRunLoopMode {
// ...
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
// ...
};
刚好可以总结为下面这一张图
那么这些
items(事务)是如何依赖于Mode在runloop中run起来的呢?
runloop如何处理事务
我们就Timer的流程来了解下,我们知道给runloop添加一个Timer事务OC是这么写的
{
CFRunLoopTimerContext context = {
0,
((__bridge void *)self),
NULL,
NULL,
NULL
};
CFRunLoopRef rlp = CFRunLoopGetCurrent();
/**
参数一:用于分配对象的内存
参数二:在什么是触发 (距离现在)
参数三:每隔多少时间触发一次
参数四:未来参数
参数五:CFRunLoopObserver的优先级 当在Runloop同一运行阶段中有多个CFRunLoopObserver 正常情况下使用0
参数六:回调,比如触发事件,我就会来到这里
参数七:上下文记录信息
*/
CFRunLoopTimerRef timerRef = CFRunLoopTimerCreate(kCFAllocatorDefault, 0, 1, 0, 0, lgRunLoopTimerCallBack, &context);
CFRunLoopAddTimer(rlp, timerRef, kCFRunLoopDefaultMode);
}
打开CFRunLoop的源码
static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
CFStringRef modeName = (CFStringRef)value;
CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);
if (CFGetTypeID(item) == __kCFRunLoopSourceTypeID) {
CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
} else if (CFGetTypeID(item) == __kCFRunLoopObserverTypeID) {
CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
} else if (CFGetTypeID(item) == __kCFRunLoopTimerTypeID) {
CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName);
}
}
进入到CFRunLoopAddTimer我们看到首先匹配当前的模式modeName == kCFRunLoopCommonModes,找到rl->_commonModes的集合,非空的话就把当前的Timer事务和对应的回调函数__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__加进去, 那么这个回调函数什么时候调用呢?来到了上面提到的CFRunLoopRun这个函数,接着顺着方法调用顺序来到了CFRunLoopRunSpecific函数,这个函数在即将进入runLoop和即将退出runLoop有一步操作
在__CFRunLoopRun中找到和Timer事务相关的代码
在__CFRunLoopDoTimers中可以看到遍历所有模式下的timers然后执行__CFRunLoopDoTimer
__CFRunLoopDoTimer方法中,找到对应的rlm也就是CFRunLoopModeRef,然后解锁runloop执行回调
所以整体的流程就是CFRunLoopRunSpecific-> __CFRunLoopRun -> __CFRunLoopDoTimers -> __CFRunLoopDoTimer -> __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
其他的事务大致流程都是一致的
runloop原理
在上面分析Timer的时候,提到了
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5), // 生命周期
kCFRunLoopAfterWaiting = (1UL << 6), // 生命周期
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
其实最主要的处理逻辑都在**__CFRunLoopRun**里面,在一个do-while循环里面
- 通知Observers:即将处理timer事件
- 通知Observers:即将处理Source事件
- 处理blocks
- 处理source0: 如果source0=true,处理blocks
- 判断有无端口消息(source1)有的话处理,然后唤醒runloop, source1具备runloop的唤醒功能
- 通知Observers:线程即将休眠
总结起来就是下面这张图,相信我们都不陌生。
那么runloop在实际的项目开发中有什么用处呢,我们下一篇“界面优化”见。