RunLoop,从字面意思看叫做运行循环。RunLoop基本作用:
- 保持程序的持续运行
- 处理APP中的各种事件(触摸事件,定时器事件...)
- 节省CPU资源,提高程序性能:有事做事,没事休息
等等......
RunLoop内部其实就是一个do-while循环,在这个循环内部不断的处理各种事件,因此保证了程序不会退出一直运行;
程序的main函数中,UIApplicationMain函数内部自动启动了一个RunLoop,所以UIApplicationMain一直没有返回,保持程序持续运行。这个默认启动的RunLoop是跟主线程相关联的;
RunLoop对象:
iOS中有2套API访问和使用RunLoop:
- Foundation框架下的:NSRunLoop
- CoreFoundation框架下的:CFRunLoopRef;
CFRunLoopRef和NSRunLoop都代表RunLoop对象;NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层的API;
获取RunLoop对象
// Foundation:
[NSRunLoop currentRunLoop];// 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
//Core Foundation:
CFRunLoopGetCurrent();// 获得当前线程的RunLoop对象
CFRunLoopGetMain();// 获得主线程的RunLoop对象
获取RunLoop的源码如下
// 存放Runloop的全局变量__CFRunLoops,key是pthread_t,value是Runloop
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFSpinLock_t loopsLock = CFSpinLockInit;
// 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为0或为空表示主线程
t = pthread_main_thread_np();
}
__CFSpinLock(&loopsLock);
//__CFRunLoops为空,则初始化__CFRunLoops,并创建主线程的Runloop,保存到__CFRunLoops中
if (!__CFRunLoops) {
__CFSpinUnlock(&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);
__CFSpinLock(&loopsLock);
}
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
// 通过pthread_t获取loop,如果没获取到,则新建一个loop保存到__CFRunLoops中,返回该loop
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFSpinLock(&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
__CFSpinUnlock(&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;
}
可参考苹果官方文档:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
苹果开源代码:
http://opensource.apple.com/source/CF/
CFRunLoopRef
源码是这样定义CFRunLoopRef的:
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __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; // runloop对应的线程
uint32_t _winthread;
CFMutableSetRef _commonModes; //被标记为commonMode的模式
CFMutableSetRef _commonModeItems;//添加到commonMode的item
CFRunLoopModeRef _currentMode; //Runloop当前的运行的模式
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};
从代码中可以看出Runloop主要包含线程pthread_t和模式CFRunLoopModeRef。_commonModes存放已被标记为“common”的mode。主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode,这两个 Mode 都已经被标记为”Common”属性。_commonModeItem是哪些被添加到commonMode的source/timer/observer。
RunLoop与线程的关系
- 每条线程都有唯一一个与之对应的RunLoop对象;
- Runloop保存在一个全局的dictionary中,线程为key,Runloop为value;
- 主线程的RunLoop程序启动后自动创建好了,而子线程的RunLoop在第一次获取时创建;
- RunLoop在线程结束时销毁;
RunLoop与RunLoopMode的关系
- RunLoopMode代表RunLoop的运行模式,每个RunLoop包含若干个Mode
- 每次RunLoop启动时,只能指定其中一个Mode,这个Mode称作CurrentMode,Runloop循环处理CurrentMode的所有事件;
- 如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入;这样可以分隔开不同mode的source、timer、observer,互不影响;
- 如果Mode里没有任何source、timer、observer,Runloop立刻退出;
CFRunLoopModeRef(模式)
源码中对CFRunLoopModeRef的定义:
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
....
};
从源码中可以看到每个Mode包含若干个source0、source1、timer、observer;
系统默认注册了5个Mode:
KCFRunLoopDefaultMode:APP的默认Mode,通常主线程在这个Mode下运行;
UITrackingRunLoopMode:界面跟踪Mode,用于scrollView追踪触摸滑动,保证界面滑动不受其他Mode影响;
UIInitializationRunLoopMode:在刚启动APP时进入的第一个Mode,启动完成后就不再使用;
GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到;
KCFRunloopCommonModes:这是一个占位用的Mode,不是真正的Mode;事件被标记为KCFRunloopCommonMode后,Runloop运行在UITrackingRunLoopMode和kCFRunLoopDefaultMode,事件都能执行;
CFRunLoopSourceRef(事件源)
Source0:触摸事件、performSelector:onThread:
Source1:基于Port的线程间通信、系统事件捕捉,其回调函数为 __IOHIDEventSystemClientQueueCallback()。
当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收。SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。
_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。
RunLoopTimer
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 */
};
RunLoopTimer是基于时间的触发器,包含一个时间长度和回调。NSTimer注册到Runloop后,Runloop会为重复的时间点注册好事件,时间间隔_interval,时间点到时Runloop会被唤醒执行回调;Runloop为了节省资源,不会准确按照时间点回调timer,_tolerance表示到了时间点后允许的最大误差。如果某个时间点被错过了,则这个时间点的回调会跳过,不会延后执行。
NSTimer的处理,会受到RunLoop的Mode影响。举个例子:
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
上面这样创建的处理的time只能在NSDefaultRunLoopMode下工作,当滚动页面时,Runloop切换到UITrackingRunLoopMode,timer失效;若想在NSDefaultRunLoopMode和UITrackingRunLoopMode模式下,timer都有效,应将timer事件添加到KCFRunloopCommonModes;如下:
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop]addTimer:timer forMode: KCFRunloopCommonModes];
RunLoopObserver
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 */
};
RunLoopObserver是用来观察Runloop的状态的。当Runloop状态发生改变,观察者能通过回调接收到这个变化。可检测到Runloop以下状态:
/* 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
};
Runloop的运行逻辑
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
从源码可以看到,Runloop做着do...while循环,CFRunLoopRunSpecific函数返回值为kCFRunLoopRunStopped或kCFRunLoopRunFinished时退出循环。
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
// 通知observer即将进入Runloop
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// Runloop循环处理事件,__CFRunLoopRun函数只有在被打断或没有source/timer/observer时,才会返回
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
//通知observer,Runloop即将退出
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
return result;
}
进入CFRunLoopRunSpecific函数,说明Runloop将要开始工作了,于是通知observer即将进入Runloop。然后调用__CFRunLoopRun函数循环处理事件,函数返回,通知observer,Runloop即将退出。直到Runloop被中断或事件处理完毕,整个Runloop循环结束;
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
Boolean didDispatchPortLastTime = true;
int32_t retVal = 0;
do {
// 通知 Observers: RunLoop 即将触发 Timer 回调。
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 执行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
// RunLoop 触发 Source0 (非port) 回调。
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
// 执行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
}
// 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
goto handle_msg;
}
// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
/*
调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒
• 一个基于 port 的Source 的事件。
• 一个 Timer 到时间了
• RunLoop 自身的超时时间到了
• 被其他什么调用者手动唤醒
*/
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
// 通知 Observers: RunLoop 的线程刚刚被唤醒了。
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
// 收到消息,处理消息。
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
// Timer 到时间了,触发Timer的回调。
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time()))
}
//有dispatch回到main_queue的block,执行block。
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else {
// Source1 (基于port) 发出事件,处理这个事件
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// Despite the name, this works for windows handles as well
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
}
}
// 执行加入到Loop的block
__CFRunLoopDoBlocks(rl, rlm);
if (sourceHandledThisLoop && stopAfterHandle) {
// 进入loop时参数说处理完事件就返回。
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)) {
// source/timer/observer一个都没有了
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
return retVal;
}
__CFRunLoopRun函数是进入Runloop后的处理逻辑。
RunLoop整个运行过程如下:
1.通知Observer,将要进入RunLoop
2.通知Observer,将要处理timer
3.通知Observer,即将触发非基于端口的输入源(source0事件)。
4.处理block
5.触发的source0事件(可能会再次处理)
6.如果存在source1,调转到第8步
7.通知Observer开始休眠
8.通知Observer结束休眠
1、处理timer
2、处理GCD Async To Main Queue
3、处理source19.处理Blocks
10.根据前面执行的结果,决定如何操作
1、回到第2步
2、退出RunLoop11.通知Observer退出RunLoop
9.处理待处理事件。
如果触发了用户定义的计时器,则处理计时器事件并重新启动循环。转到第2步。
如果输入源被触发,则传递事件。
如果运行循环被明确唤醒但尚未超时,请重新启动循环。转到第2步。10.通知观察者运行循环已退出。
参考博客:
深入理解RunLoop