**** 一.概述 ****

一般来讲,开一个线程执行某项任务,在任务执行完成后线程就会退出。如果我们需要让线程能不退出一直常驻,随时处理事件这就需要消息循环来实现了。
Runloop 是 iOS中的消息循环,Android也有Looper/Handler一套消息循环机制,这到底是啥,以前玩过单片机的小伙伴肯定记得很清楚,我们在配置完硬件,对各个部件进行初始化完毕后,都会在main中执行一个死循环,然后等待硬件中断,软件中断,在对应的中断函数中处理外部事件,其实这个死循环就是我们今天要介绍的Runloop,它会保持整个应用进程处于存活状态,然后在每个消息循环中处理各个事件。Runloop是我们平常看不见摸不着,但是又比不可少的一个系统组件。

Runloop 的思想可以用下面的伪代码来表示:

int main(void) {
初始化();
while (不满足退出条件) {
休眠前处理待处理事件(message);
睡眠ZZzzzz... 等待被事件唤醒
message = 获取需要处理的事件();
处理事件(message)
}
return 0;
}

依赖NSRunloop的类和框架

NSTimer
UIEvent
autorelease
NSObject(NSDelaydPerforming)
NSObject(NSThreadPerformAddtion)
CADisplayLink
CATransition
CAAnimation
dispatch_get_main_queue()
**** 二.RunLoop 需要注意的点:****
  • CFRunLoopRef基于C线程安全,NSRunLoop基于CFRunLoopRef面向对象的API是不安全的.

  • RunLoop和线程的一一对应的,对应的方式是以key-value的方式保存在一个全局字典中,

  • 主线程的 Runloop 会在应用启动的时候启动,其他线程的 Runloop需要我们手动创建并启动,RunLoop在第一次获取时创建,在线程结束时销毁,我们只能在一个线程的内部获取其RunLoop,并且苹果系统不允许我们直接创建RunLoop对象,只能通过以下几个函数来获取RunLoop:

    CFRunLoopRef CFRunLoopGetCurrent(void)
    CFRunLoopRef CFRunLoopGetMain(void)
    +(NSRunLoop *)currentRunLoop
    +(NSRunLoop *)mainRunLoop
  • Runloop Mode是 Source,Timer 和 Observer 的集合,不同的 Mode 把不同组的 Source,Timer 和 Observer隔绝开。Runloop 在某个时刻只能跑在一个Mode下,处理这一个Mode 当中的 Source,Timer 和 Observer,这也是为啥在滑动屏幕的时候有时候定时器会失效的原因,这个会在后续介绍。

  • Source0是内部事件 UIEvent、CFSocket都是source0,它区别于source1,它不是基于Port的,它不能主动触发事件。我们需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop处理这个事件。

  • Source1由RunLoop和内核管理,source1带有mach_port_t和一个回调,可以接收内核消息并触发回调,它能主动唤醒 RunLoop 的线程。

  • source0 有公开的 API 可供开发者调用,source1 却只能供系统使用

  • Timmer 包含一个时间长度和一个回调。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
    NSTimer的创建通常有两种方式,一种是以timer开头,另一种是以schedued开头,第一种是没有添加到Runloop中的,需要我们自己去手动调用代码添加,第二种会自动以NSDefaultRunLoopModeMode添加到当前线程RunLoop中。

  • 一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环

三. RunLoop 关键源码解析:
3.1 RunLoop 关键数据结构
  • __CFRunLoop
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; // 用来锁对于 mode 列表的访问
__CFPort _wakeUpPort; // 用来唤醒 run loop 的 mach port
Boolean _unused;
volatile _per_run_data *_perRunData;
pthread_t _pthread; //对应的 pthread
uint32_t _winthread; //Windows 下对应线程
CFMutableSetRef _commonModes; //[存放 common mode 的集合]
CFMutableSetRef _commonModeItems; //[每个 common mode 都有的 item (source, timer and observer) 集合]
CFRunLoopModeRef _currentMode; //当前 run 的 mode
CFMutableSetRef _modes; //这个 run loop 所有的 mode 集合
struct _block_item *_blocks_head; //存放 CFRunLoopPerformBlock 函数添加的 block 的双向链表的头指针
struct _block_item *_blocks_tail; //存放 CFRunLoopPerformBlock 函数添加的 block 的双向链表的尾指针
CFAbsoluteTime _runTime; //总共的运行时间
CFAbsoluteTime _sleepTime; //总共的睡眠时间
CFTypeRef _counterpart;
};

每个__CFRunLoop 会存储有:

  • 当前RunLoop所对应的线程****_pthread****

  • 当前RunLoop所能运行的所有模式****_modes以及它的锁_lock****

  • 当前RunLoop当前运行模式****_modes****

  • 当前RunLoop当前运行模式****_currentMode****

  • 当前RunLoop的公用模式信息****_commonModes_commonModeItems****

  • 当前RunLoop唤醒端口****_wakeUpPort****

  • 添加到当前Runloop的Block信息****_blocks_head,_blocks_tail****

  • 当前RunLoop相关的统计信息****_runTime,_sleepTime****

  • __CFRunLoopMode

struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* 一个mutex,锁mode里的各种操作。根据注释,需要runloop 的锁先锁上才能锁这个锁。 */
CFStringRef _name; /* 模式名称 */
Boolean _stopped; //是否停止了
char _padding[3];
CFMutableSetRef _sources0; //Source0 集合,也就是非 port 的 source
CFMutableSetRef _sources1; //Source1 集合,也就是基于 port 的 source
CFMutableArrayRef _observers; //Observer 集合
CFMutableArrayRef _timers; //Timer 集合
CFMutableDictionaryRef _portToV1SourceMap; //Key 是 port,value 是对应 source1 的字典
__CFPortSet _portSet; //所有 port 的集合
CFIndex _observerMask; //需要 observe 的事件的 mask
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource; //用来实现 timer 的 GCD timer
dispatch_queue_t _queue; //放 _timerSource 的队列
Boolean _timerFired; // _timerSource 是否被启动
Boolean _dispatchTimerArmed; //timer是否开启了
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort; //使用 MK timer 时的端口
Boolean _mkTimerArmed; //timer 是否被开启
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* //下一个计划启动的时间 */
uint64_t _timerHardDeadline; /* 下一个最迟启动的时间(计划加上容忍延迟的时间)*/
};

__CFRunLoopMode 里面最关键的元素包括:****_sources0****,_sources1,_observers,_timers,_portSet

iOS 系统中定义了如下几种Mode:

  • NSDefaultRunLoopMode:
    NSDefaultRunLoopMode是Runloop的默认的运行模式,主线程在启动的时候就是运行在这个模式下的,NSTimer与NSURLConnection默认运行该模式下。
  • UITrackingRunLoopMode:在触摸滑动界面的时候会切换到这个模式用于保证界面滑动时不受其他 Mode 影响
  • UIInitializationRunLoopMode:在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
  • NSRunLoopCommonModes:这是一个 Mode 的集合。注册到这个 Mode 下后,无论当前 runLoop 运行哪个 mode ,事件都能得到执行。默认包括 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode
  • __CFRunLoopSource
    struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits; //用于标记Signaled状态,source0只有在被标记为Signaled状态,才会被处理
    pthread_mutex_t _lock;
    CFIndex _order; /* immutable */
    CFMutableBagRef _runLoops;
    union {
    CFRunLoopSourceContext version0; /* immutable, except invalidation */
    CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
    } _context;
    };

Source0是App内部事件,由App自己管理的UIEvent、CFSocket都是source0。当一个source0事件准备执行的时候,必须要先把它标记为signal状态,标记信息位于****_bits****

  • __CFRunLoopObserver
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 */
};

__CFRunLoopObserver 里面最关键的字段是****_callout****,它会在通知发出的时候被回调。

  • __CFRunLoopTimer
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 */
};

__CFRunLoopTimer 里面主要包含了触发的时机,以及对应的回调方法。

在针对RunLoop源码进行解析的时候大家可以针对下面这张图进行理解。下面的解析也是围绕者这张图来分析的:

3.2 RunLoop 起点

前面提到除了主线程外,其他我们自己创建的线程是没有RunLoop的,我们可以通过currentRunLoop来创建,而主线程的RunLoop是通过mainRunLoop创建的,最终会调用CFRunLoopRef 下的CFRunLoopGetCurrentCFRunLoopGetMain

CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}

CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}

上面的这两个方法都会归到一处入口,****_CFRunLoopGet0****传入的是当前所在的线程:

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
//检查当前传入的线程对象,如果传入的不合法,就使用主线程替代t
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
//如果存储RunLoop的字典不存在
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
//创建主线程的RunLoop
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
//创建一个主线程的runloop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
//将主线程 以及 主线程runloop 加入到字典中
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
//第一次进来的时候,不管是getMainRunloop还是get子线程的runloop,主线程的runloop总是会被创建
}
//通过传入的线程去获取runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
//如果字典中没有则创建新的一个runloop后加入字典
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)) {
//注册一个回调,当线程销毁时,销毁对应的RunLoop
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}

只要是第一次调用****_CFRunLoopGet0,不管是getMainRunloop还是get子线程的runloop,主线程的runloop总是会被创建,创建出来的RunLoop会被添加到__CFRunLoops这个全局字典里面。这个字典以线程指针作为key,Runloop作为value进行存储的,后续创建的一系列的Runloop都会添加到这里。每次调用_CFRunLoopGet会先从这里看下是否有已经创建好的RunLoop如果有就直接返回,如果没有则新建一个,然后存到__CFRunLoops****作为缓存。在最后的时候会判断如果传入线程就是当前线程,则会注册一个回调,当线程销毁时,销毁对应的RunLoop。

3.3 RunLoop 的创建
static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
CFRunLoopRef loop = NULL;
CFRunLoopModeRef rlm;
uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, CFRunLoopGetTypeID(), size, NULL);
if (NULL == loop) {
return NULL;
}
(void)__CFRunLoopPushPerRunData(loop);
__CFRunLoopLockInit(&loop->_lock);
loop->_wakeUpPort = __CFPortAllocate();
if (CFPORT_NULL == loop->_wakeUpPort) HALT;
__CFRunLoopSetIgnoreWakeUps(loop);
loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
loop->_commonModeItems = NULL;
loop->_currentMode = NULL;
loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
loop->_blocks_head = NULL;
loop->_blocks_tail = NULL;
loop->_counterpart = NULL;
loop->_pthread = t;
#if DEPLOYMENT_TARGET_WINDOWS
loop->_winthread = GetCurrentThreadId();
#else
loop->_winthread = 0;
#endif
rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
return loop;
}

这里没啥亮点。我们继续看下一步:

3.4 启动RunLoop

在什么都没有指定的时候,CFRunLoopRun默认是跑在kCFRunLoopDefaultMode模式下的

void CFRunLoopRun(void) {	/* DOES CALLOUT */
int32_t result;
do {
//运行当前线程的Runloop在默认模式
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
//CFRunLoopRun 函数不主动调用 CFRunLoopStop 函数(kCFRunLoopRunStopped 的情况)或者将所有事件源移除(kCFRunLoopRunFinished 的情况)是没有办法退出的
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

后续的各种run最终的入口都是归结到CFRunLoopRunSpecific

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
CHECK_FOR_FORK();
// 检查 run loop 是否正在销毁,如果销毁返回kCFRunLoopRunFinished,表示Runloop运行结束
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
//根据modeName找到本次运行的mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
/// 没有找到 mode 或者 mode 里面没有任何事件源的话,返回 kCFRunLoopRunFinished
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;

// 通知 observers: kCFRunLoopEntry, 进入 run loop
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知 observers: `kCFRunLoopExit`, 退出 run loop
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

// 恢复之前的状态
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}

在CFRunLoopRunSpecific中会通过传入的modeName,找到对应的CFRunLoopModeRef,并将其传入****__CFRunLoopRun作为当前RunLoop要跑的模式。进入和退出__CFRunLoopRun会发出kCFRunLoopEntry,和kCFRunLoopExit****通知外部的Observer Runloop将要开始,以及Runloop已经退出。

__CFRunLoopRun方法有点长,我省略了一些不必要的代码:

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
//.....
do {
//......

// 通知 observers: kCFRunLoopBeforeTimers, 即将处理 timers
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知 observers: kCFRunLoopBeforeSources, 即将处理 sources
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

// 执行加入当前runloop的block
__CFRunLoopDoBlocks(rl, rlm);
// 处理 sources 0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
// 如果实际处理了 sources 0,再一次处理blocks
__CFRunLoopDoBlocks(rl, rlm);
}
//.....
// 通知 observers: kCFRunLoopBeforeWaiting, 即将进入等待(睡眠)
// 注意到如果实际处理了 source 0 或者超时了,不会进入睡眠,所以不会通知
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 设置标志位,正在睡眠
__CFRunLoopSetSleeping(rl);

__CFRunLoopSetIgnoreWakeUps(rl);

// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);

// 通知 observers: kCFRunLoopAfterWaiting, 即停止等待(被唤醒)
// 注意实际处理过 source 0 或者已经超时的话,不会通知(因为没有睡)
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
// 被什么唤醒就处理什么:
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);

//// 不知道哪个端口唤醒的(或者根本没睡),啥也不干
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
//// 被 CFRunLoopWakeUp 函数弄醒的,啥也不干
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// do nothing on Mac OS
}
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);
}
}
// 被 GCD 唤醒处理 GCD
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
//.....
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
//....
} else {
/// 被 sources 1 唤醒,处理 sources 1
CFRUNLOOP_WAKEUP_FOR_SOURCE();
//.......
}
//....
} while (0 == retVal);
//....
return retVal;
}

大体流程大家可以结合代码注释看下图中所示。

  1. 通知将要处理Timmer事件
  2. 通知将要处理Source事件
  3. 处理Source0事件,在处理Source0事件之前都会调用****__CFRunLoopDoBlocks****处理下block。
  4. 通知将要进入睡眠状态
  5. Runloop进入睡眠状态
  6. 当有事件源给Runloop发送消息,表示有事件处理的时候,就会唤醒Runloop。
  7. 唤醒Runloop。
  8. 通知Runloop已经唤醒。
  9. 根据事件源来调用不同的方法来执行处理,这里的事件源可以是Timmer,Source1,GCD事件。
  10. 继续上面循环。
四 RunLoop 应用相关

  • AutoreleasePool
    RunLoop的进入的时候会调用objc_autoreleasePoolPush()创建新的自动释放池。
    RunLoop的进入休眠的时候会调用objc_autoreleasePoolPop() 和 objc_autoreleasePoolPush() 销毁自动释放池,创建一个新的自动释放池。
    RunLoop即将退出时会调用objc_autoreleasePoolPop() 释放自动自动释放池内对象。

  • GCD

当调用了dispatch_async(dispatch_get_main_queue())时libDispatch会向主线程RunLoop发送消息唤醒RunLoop,RunLoop从消息中获取block,并且在CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE回调里执行这个block。需要注意的是dispatch_async() 到其他线程是由libDispatch处理,并不涉及到RunLoop。

  • 支持异步方法调用

我们使用 performSelector:onThread: 或者 performSelecter:afterDelay: 时,实际上系统会创建一个Timer并添加到当前线程的RunLoop中

  • NSTimer, GCD Timer,CADisplayLink

NSTimer 的执行必须依赖于 RunLoop,如果没有 RunLoop,NSTimer 是不会执行的。而GCD 的线程管理是通过系统来直接管理的。GCD Timer 是通过 dispatch port 给 RunLoop 发送消息,来使 RunLoop 执行相应的 block,如果所在线程没有 RunLoop,那么 GCD 会临时创建一个线程去执行 block,执行完之后再销毁掉,因此 GCD 的 Timer 是不依赖 RunLoop 的。CADisplayLink是一个执行频率和屏幕刷新相同的定时器,它也需要加入到RunLoop才能执行,通常情况下CADisaplayLink用于构建帧动画,看起来相对更加流畅。

子线程启动定时器需要按照如下方式启动Runloop

dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSTimer* timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(Timered:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
});

  • 滑动屏幕导致定时器失效

一般而言

[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

等效于

NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopDefaultModes];

当我们滚动ScrollView或者滑动屏幕的时候,RunLoop会切换到UITrackingRunLoopMode 模式,而定时器运行在defaultMode下面,系统一次只能处理一种模式的RunLoop,所以导致defaultMode下的定时器失效,所以这种情况的解决方式就是将定时器放到commonMode中,这样即使切换到UITrackingRunLoopMode也会被触发。

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

或者使用GCD定时器,它不会受RunLoop的影响:

// 获得队列
dispatch_queue_t queue = dispatch_get_main_queue();

// 创建一个定时器(dispatch_source_t本质还是个OC对象)
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

// 设置定时器的各种属性(几时开始任务,每隔多长时间执行一次)
// GCD的时间参数,一般是纳秒(1秒 == 109次方纳秒)
// 比当前时间晚1秒开始执行
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));

//每隔一秒执行一次
uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
dispatch_source_set_timer(self.timer, start, interval, 0);

// 设置回调
dispatch_source_set_event_handler(self.timer, ^{
});

// 启动定时器
dispatch_resume(self.timer);
  • 触摸事件

当一个硬件事件发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收。随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。
_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,

  • 屏幕刷新

在我们修改了View frame、或者调整了UI层级,或者手动设置了setNeedsDisplay/setNeedsLayout之后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去,_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv 这个Observer会监听RunLoop即将进入休眠和退出状态,一旦进入这两种状态则会遍历所有的UI更新并提交进行实际绘制更新。facebook推出的AsyncDisplayKit的机制和它也类似,它将UI排版和绘制运算尽可能放到后台,将UI的最终更新操作放到主线程,在主线程RunLoop中增加了一个Observer监听即将进入休眠和退出RunLoop两种状态,收到回调时遍历队列中的待处理任务更新界面。

  • 常驻线程

我们有时候需要创建一个在后台一直存在的程序,来做一些需要频繁处理的任务

- (void)run {
@autoreleasepool{
/*如果不加这句,会发现runloop创建出来就挂了,因为runloop如果没有CFRunLoopSourceRef事件源输入或者定时器,就会立马消亡。
下面的方法给runloop添加一个NSPort,就是添加一个事件源,也可以添加一个定时器,或者observer,让runloop不会挂掉*/
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];

[[NSRunLoop currentRunLoop] run];
}
}

当需要这个后台线程执行任务时,通过调用 [NSObject performSelector:onThread:..] 将这个任务扔到了后台线程的 RunLoop 中。

AFNetworking 就是通过这种方式接收 Delegate 回调


+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// 这里主要是监听某个 port,目的是让RunLoop不会退出,确保该 Thread 不会被回收
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}

+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread =
[[NSThread alloc] initWithTarget:self
selector:@selector(networkRequestThreadEntryPoint:)
object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
  • 观察Runloop状态
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler
(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity _) {
// TODO here
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
CFRelease(observer);
Contents
  1. 1. **** 一.概述 ****
  2. 2. **** 二.RunLoop 需要注意的点:****
  3. 3. 三. RunLoop 关键源码解析:
  4. 4. 3.1 RunLoop 关键数据结构
  5. 5. 3.2 RunLoop 起点
  6. 6. 3.3 RunLoop 的创建
  7. 7. 3.4 启动RunLoop
  8. 8. 四 RunLoop 应用相关