RunLoop源码详解

RunLoop是什么

如果没有RunLoop,那么该线程在执行完自己的任务时就会退出;而我们在使用APP时,通常不会只执行完一个任务就需要其退出,那么我们就需要一种机制,在我们没有任务时依然可以保证线程不退出,随时可以处理事件,这就需要用到RunLoop了。
RunLoop,顾名思义:运行循环,在其内部就是一个大的do-while循环,在这个循环里执行了各种操作,保证线程不退出,程序持续运行。其存在的目的就是:在保证线程不退出的前提下,当线程有任务时执行任务,在线程没有任务的时候就让其睡眠,节省CPU资源,提高程序性能。

NSRunLoop和CFRunLoop

苹果官方源码
我们平时所说的RunLoop有两种,一种是NSRunLoop,一种是CFRunLoop
将下列代码写在主线程中,即主RunLoop和当前的RunLoop是同一个

NSLog(@"%p %p", [NSRunLoop mainRunLoop], [NSRunLoop currentRunLoop]);
NSLog(@"%p %p", CFRunLoopGetMain(), CFRunLoopGetCurrent());

在这里插入图片描述
打印结果显示NSRunLoop和CFRunLoop地址不一样,再打印NSRunLoop的信息看看

NSLog(@"%@", [NSRunLoop mainRunLoop]);
NSLog(@"%p %p", CFRunLoopGetMain(), CFRunLoopGetCurrent());

在这里插入图片描述
根据箭头所指可以得知,NSRunLoop只是比CFRunLoop多了一层简单的OC封装,底层还是CFRunLoop,CFRunLoop本质是一个结构体,而NSRunLoop是一个NSObject对象。且NSRunLoop存在于Foundation框架中,CFRunLoop是存在于CoreFoundation框架中的。NSRunLoop不是线程安全的,CFRunLoop时候线程安全的。那我们现在看看CFRunLoop是如何实现的

RunLoop内部结构

在分析源码之前,我们先大致了解一下RunLoop的内部结构。CoreFoundation里面关于RunLoop有五个类:

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

他们之间的关系为:(图来源于深入理解RunLoop
在这里插入图片描述
即一个RunLoop里有许多个Mode,一个Mode里有一个Source的set,一个Observer的array和一个Timer的array。这里要注意,虽然一个RunLoop里存在许多Mode,但是每次只能使用一个Mode,需要更换时就退出当前Mode,换下一个Mode

RunLoop基本结构体源码

__CFRunLoop结构体

我们先看看__CFRunLoop结构体是怎么组成的

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;			/* locked for accessing mode list */  // 访问mode集合时用到的锁
    __CFPort _wakeUpPort;			// used for CFRunLoopWakeUp   // 手动唤醒runloop的端口。初始化runloop时设置,仅用于CFRunLoopWakeUp,CFRunLoopWakeUp函数会向_wakeUpPort发送一条消息
    Boolean _unused;
    volatile _per_run_data *_perRunData;           // reset for runs of the run loop  初始化runloop时设置,仅用于CFRunLoopWakeUp,CFRunLoopWakeUp函数会向_wakeUpPort发送一条消息
    pthread_t _pthread;                            // 与该RunLoop关联的线程
    uint32_t _winthread;
    CFMutableSetRef _commonModes;                  // 存储的是字符串,记录所有标记为common的mode
    CFMutableSetRef _commonModeItems;              // 存储所有commonMode的item(source、timer、observer)
    CFRunLoopModeRef _currentMode;                 // 当前运行的mode
    CFMutableSetRef _modes;                        // 该RunLoop中所有的mode
    struct _block_item *_blocks_head;              // 链表头指针,该链表保存了所有需要被runloop执行的block。外部通过调用CFRunLoopPerformBlock函数来向链表中添加一个block节点。runloop会在CFRunLoopDoBlock时遍历该链表,逐一执行block
    struct _block_item *_blocks_tail;              // 链表尾指针,之所以有尾指针,是为了降低增加block时的时间复杂度
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

从__CFRunLoop结构体可以得知:

  1. RunLoop和线程是一一对应的,一个RunLoop包含一个线程,当该线程结束时RunLoop自动销毁
  2. 一个RunLoop只有一个_currentMode,即当前运行的mode
  3. _commonModes 表示NSRunLoopCommonModes这个模式下保存的Mode,我们也可以将自定义的Mode添加到这个set里面
  4. _commonModeItem 表示添加到NSRunLoopCommonModes里面的Source/Timer/Observer等
  5. _commonModes里面保存的是被标记为common的mode。这种标记为common的mode有种特性,那就是当 RunLoop 的内容发生变化时,RunLoop 都会自动将 commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。可以这样理解,RunLoop中的_commonModeItems由被标记为common的mode下的各个item(source、observe、timer)组成

__CFRunLoopMode结构体

上文谈到了许多次mode,现在看看mode是怎么组成的:

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;	/* must have the run loop locked before locking this */
    CFStringRef _name;     // mode名称
    Boolean _stopped;      // mode是否被终止
    char _padding[3];
    // 几种事件,下面这四个字段,在苹果官方文档里面称为Item
    // RunLoop中有个commomitems字段,里面就是保存的下面这些内容
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    CFMutableDictionaryRef _portToV1SourceMap;   //字典  key是mach_port_t,value是CFRunLoopSourceRef
    __CFPortSet _portSet;    //保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中
    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 */
};

需要注意:

  1. 一个CFRunLoopMode对象中有一个name,许多source0,许多source1,许多Timer,许多Observer和许多port
  2. 我们在下文会提到的kCFRunLoopDefaultMode就是mode的name,当你传入一个新的 mode name 但 RunLoop 内部没有对应 mode 时,RunLoop会自动帮你创建对应的 CFRunLoopModeRef
  3. 对于一个 RunLoop 来说,其内部的 mode 只能增加不能删除
  4. source、timer、observer可以在多个mode中注册,但是只有RunLoop当前的currentMode下的source、timer、observer才可以运行

RunLoop中的mode主要分为以下几种:

  1. KCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
  2. UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动的时候不受其他Mode影响
  3. KCFRunLoopCommonMode:这是一个占位用的Mode,作为标记KCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode(也可以理解为是上面两种mode的结合)
  4. UIInitializationRunLoopMode:在刚启动App的时候进入的第一个Mode,启动完成后不再使用
  5. CSEventReceiveRunLoopMode:接受系统事件内部Mode,通常用不到

综合以上,我们可以得知,RunLoop里只保存了许多mode,而mode里保存的才是实际执行的任务
接下来我们剖析mode里包含的三种item

__CFRunLoopSource结构体

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;    //用于标记Signaled状态,source0只有在被标记为Signaled状态,才会被处理
    pthread_mutex_t _lock;
    CFIndex _order;			/* immutable */
    CFMutableBagRef _runLoops;
    union {
        // version0、version1 是根据对不同事件的处理区分出来的source0、source1
		CFRunLoopSourceContext version0;	/* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;	/* immutable, except invalidation */
    } _context;
};

可以看出,Source根据要处理的事件不同分为两个版本,version0和version1,我们分别探究两个的结构体:

CFRunLoopSourceContext

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, CFStringRef mode);  //当source加入到mode触发的回调
    void	(*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);    //当source从RunLoop中移除时触发的回调
    void	(*perform)(void *info);  //当source事件被触发时的回调,使用CFRunLoopSourceSignal方式触发
} CFRunLoopSourceContext;

即:
source0 只包含了回调(函数指针),source0是需要手动触发的Source,它并不能主动触发事件,必须要先把它标记为signal状态。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,也就是通过uint32_t _bits来实现的,只有_bits标记Signaled状态才会被处理。然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件

CFRunLoopSourceContext1

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_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
    mach_port_t	(*getPort)(void *info);   //当source被添加到mode中的时候,从这个函数中获得具体mach_port_t
    void *	(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
    void *	(*getPort)(void *info);
    void	(*perform)(void *info);
#endif
} CFRunLoopSourceContext1;

source1除了多个了getPort。其余的字段含义和source0相同。作用就是当source被添加到mode中的时候,从这个函数中获得具体mach_port_t。这种 Source 能主动唤醒 RunLoop 的线程。简单来说就是更加偏向于底层

__CFRunLoopTimer结构体

__CFRunLoopTimer主要处理RunLoop的相关Timer事件,定时器,定时执行一个任务,也是在RunLoop中处理的

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;    //标记fire状态
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;       //添加该timer的RunLoop
    CFMutableSetRef _rlModes;    //存放所有包含该timer的 mode的 modeName,意味着一个timer可能会在多个mode中存在
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;		//理想时间间隔    /* immutable */
    CFTimeInterval _tolerance;      //时间偏差        /* mutable */
    uint64_t _fireTSR;			/* TSR units */
    CFIndex _order;			/* immutable */
    CFRunLoopTimerCallBack _callout;	 //设置回调函数,回调指针    /* immutable */
    CFRunLoopTimerContext _context;	/* immutable, except invalidation */
};

Timer包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。如果当前时间点正在执行别的任务,会直接越过Timer的任务。类似于在18:00和18:10都有公交车,你玩手机错过了18:00的车就直接等18:10的车

注意: NSTimer在添加到runloop中的时候要用NSRunLoopCommonModes作为参数。
理由:并且NSTimer创建好之后默认是加入名为kCFRunLoopDefaultMode的model中,所以只有应用程序在kCFRunLoopDefaultMode下,Timer才会工作。如果这个时候滑动了屏幕,那么应用的mode就从名字为kCFRunLoopDefaultMode的mode中退出,进入到名称为UITrackingRunLoopMode的model中。因为NSTimer没有添加到名字为UITrackingRunLoopMode的item中,所以只有等到不再滑动,即回到kCFRunLoopDefaultMode的时候才再次开始工作。通过NSRunLoopCommonModes标记之后,Runloop会把NSTimer加入到Runloop中的commonModeItems中。上面讲过RunLoop 都会自动将 commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Model里。

__CFRunLoopObserver结构体

struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;   //添加该Observer的RunLoop
    CFIndex _rlCount;
    CFOptionFlags _activities;		/* immutable */
    CFIndex _order;			/* immutable */
    CFRunLoopObserverCallBack _callout;	    //设置回调函数,回调指针  /* immutable */
    CFRunLoopObserverContext _context;	/* immutable, except invalidation */
};

CFRunLoopObserver是观察者,可以观察RunLoop的各种状态,每个 Observer 都包含了一个回调(也就是上面的CFRunLoopObserverCallBack函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。RunLoop在运行过程中有以下几个状态:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),            //即将进入RunLoop
    kCFRunLoopBeforeTimers = (1UL << 1),     //即将处理timer
    kCFRunLoopBeforeSources = (1UL << 2),    //即将处理source
    kCFRunLoopBeforeWaiting = (1UL << 5),    //即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),     //被唤醒但是还没开始处理事件
    kCFRunLoopExit = (1UL << 7),             //RunLoop已经退出
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

RunLoop获取源码

上文中提到过CFRunLoopGetMain()和CFRunLoopGetCurrent()都可以获取RunLoop,前者用于获取主线程RunLoop,后者用于获取当前线程。我们查看一下两者的源码

CFRunLoopGetMain()和CFRunLoopGetCurrent()源码

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()来获取RunLoop的,那么我们看看_CFRunLoopGet0()的源码

_CFRunLoopGet0()源码

// 声明一个全局的可变字典 __CFRunLoops用于存储每一条线程和对应的runloop
// __CFRunLoops的键key存储线程pthread_t,值存储runloop
static CFMutableDictionaryRef __CFRunLoops = NULL;
// loopsLock用于访问 __CFRunLoops时加锁,锁的目的是:考虑线程安全问题,防止多条线程同时访问 __CFRunLoop对应的内存空间
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) {
    // 判断t所指向的线程是否为空,如果为空,就获取当前主线程进行赋值
    if (pthread_equal(t, kNilPthreadT)) {
	t = pthread_main_thread_np();
    }
    // 处理线程安全,加锁
    __CFLock(&loopsLock);
    // 判断全局的可变字典 __CFRunLoops是否为空
    // 如果为空
    if (!__CFRunLoops) {
        // 解锁
        __CFUnlock(&loopsLock);
        // 定义一个局部可变字典dict
	CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        // 创建一个与主线程对应的RunLoop对象mainLoop
	CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        // 把主线程和主线程对应的mainLoop存储于字典dict
	CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        // 把局部创建的字典dict赋值给全局字典__RunLoops
	if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
        // 赋值成功后,release局部变量
	    CFRelease(dict);
	}
        // 存储mainLoop到字典以后,撤销一次引用,即release一次mainLoop,因为mainLoop存储到字典后会自动retain
	CFRelease(mainLoop);
        // 加锁
        __CFLock(&loopsLock);
    }
    // 从全局的字典 __CFRunLoops中获取对应于当前线程的RunLoop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    // 解锁
    __CFUnlock(&loopsLock);
    // 如果获取不到RunLoop就创建并存储
    // 注意:在子线程中第一次获取RunLoop是获取不到的,获取不到的时候才去创建,这个if语句一般在子线程中第一次获取的时候才会执行
    if (!loop) {
        // 根据当前线程创建一个RunLoop
	CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        // 加锁
        __CFLock(&loopsLock);
        // 再一次从全局的字典 __CFRunLoops中获取对应于当前线程的RunLoop
	loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        // 如果还是获取不到RunLoop,就存储当前线程和刚创建的RunLoop到全局字典 __CFRunLoops
	if (!loop) {
        // 存储当前线程和刚创建的RunLoop到全局字典 __CFRunLoops
	    CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        // 加锁
	    loop = newLoop;
	}
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        // 解锁
        __CFUnlock(&loopsLock);
        // release局部的newLoop
	CFRelease(newLoop);
    }
    // 判断t是否为当前线程,如果是就注册一个回调,当线程销毁的时候,也销毁其对应的RunLoop
    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);
        }
    }
    // 返回当前线程对应的RunLoop
    return loop;
}

上面源码的大概逻辑为:
在这里插入图片描述

RunLoop运行源码

上面讲解了获取当前RunLoop的原理,但是在RunLoop内部是如何执行任务的呢,总的逻辑如下图(图源RunLoop从源码到应用全面解析):
在这里插入图片描述

CFRunLoopRun和CFRunLoopRunSpecific源码

CFRunLoopRun和CFRunLoopRunSpecific源码,其中只对有助于理解的代码进行了注释:

// 供外部调用的公开的CFRunLoopRun方法,其内部会调用CFRunLoopRunSpecific
void CFRunLoopRun(void) {	/* DOES CALLOUT */
    int32_t result;
    do {
        // 调用CFRunLoopRunSpecific
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

// 其内部会调用 __CFRunLoopRun 函数
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    // 首先根据modeName找到对应mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    // 如果没找到 || 如果mode里没有source/timer/observer, 直接返回
    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);
    // 取上一次运行的mode
    CFRunLoopModeRef previousMode = rl->_currentMode;
    // 如果本次mode和上次的mode一致
    rl->_currentMode = currentMode;
    // 初始化一个result为kCFRunLoopRunFinished
    int32_t result = kCFRunLoopRunFinished;
    // 通知observer:即将进入runloop
	if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    // 核心的Loop逻辑
	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;
}

注意:

  • 如果指定了一个不存在的mode来运行RunLoop,那么直接返回,mode不会被创建,所以这里传入的mode必须是存在的
  • 如果指定了一个mode,但是这个mode中不包含任何modeItem,那么RunLoop也会直接返回,所以必须要传入至少包含一个modeItem的mode
  • 在进入RunLoop之前通知observer,状态为kCFRunLoopEntry
  • 在退出RunLoop之后通知observer,状态为kCFRunLoopExit

__CFRunLoopRun函数源码

因为__CFRunLoopRun函数过长,这里只写了有助于理解的代码:

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    //记录最后runloop状态,用于return
    int32_t retVal = 0;
    do {
        //初始化一个存放内核消息的缓冲池
        uint8_t msg_buffer[3 * 1024];
        mach_msg_header_t *msg = NULL;
        mach_port_t livePort = MACH_PORT_NULL;
        //取所有需要监听的port
        __CFPortSet waitSet = rlm->_portSet;
        
        //设置RunLoop为可以被唤醒状态
        __CFRunLoopUnsetIgnoreWakeUps(rl);
        
        //2.通知observer,即将触发timer回调,处理timer事件
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        //3.通知observer,即将触发Source0回调
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        
        //执行加入当前runloop的block
        __CFRunLoopDoBlocks(rl, rlm);
        
        //4.处理source0事件
        //有事件处理返回true,没有事件返回false
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            //执行加入当前runloop的block
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        //如果没有Sources0事件处理 并且 没有超时,poll为false
        //如果有Sources0事件处理 或者 超时,poll都为true
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        
        //第一次do-while循环不会走该分支,因为didDispatchPortLastTime初始化是true
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
            //从缓冲区读取消息
            msg = (mach_msg_header_t *)msg_buffer;
            //5.接收dispatchPort端口的消息,(接收source1事件)
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
                //如果接收到了消息的话,前往第9步开始处理msg
                goto handle_msg;
            }
        }
        
        didDispatchPortLastTime = false;
        
        //6.通知观察者RunLoop即将进入休眠
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        //设置RunLoop为休眠状态
        __CFRunLoopSetSleeping(rl);
        __CFPortSetInsert(dispatchPort, waitSet);
        
        __CFRunLoopModeUnlock(rlm);
        __CFRunLoopUnlock(rl);
        
        //这里有个内循环,用于接收等待端口的消息
        //进入此循环后,线程进入休眠,直到收到新消息才跳出该循环,继续执行run loop
        do {
            //7.接收waitSet端口的消息
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
            //收到消息之后,livePort的值为msg->msgh_local_port,
            if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
                if (rlm->_timerFired) {
                    rlm->_timerFired = false;
                    break;
                } else {
                    if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
                }
            } else {
                // Go ahead and leave the inner loop.
                break;
            }
        } while (1);
        
        __CFRunLoopLock(rl);
        __CFRunLoopModeLock(rlm);
        // 醒来
        __CFPortSetRemove(dispatchPort, waitSet);
        __CFRunLoopSetIgnoreWakeUps(rl);
        
        //取消runloop的休眠状态
        __CFRunLoopUnsetSleeping(rl);
        //8.通知观察者runloop被唤醒
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
      
        //9.处理收到的消息
    handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);
        
        if (MACH_PORT_NULL == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
            //通过CFRunloopWake唤醒
        } else if (livePort == rl->_wakeUpPort) {
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
            //什么都不干,跳回2重新循环
            // do nothing on Mac OS
        }
        //如果是定时器事件
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            //9.1 处理timer事件
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
        //如果是定时器事件
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
           //9.1处理timer事件
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
        //如果是dispatch到main queue的block
        else if (livePort == dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);

            //9.2执行block
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else {
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            // 有source1事件待处理
            if (rls) {
                mach_msg_header_t *reply = NULL;
                //9.2 处理source1事件
                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
                if (NULL != reply) {
                    (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
                    CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
                }
            }
        }
        if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
        
        __CFRunLoopDoBlocks(rl, rlm);
        // 根据之前的执行结果来决定怎么做,为retVal赋相应的值
        if (sourceHandledThisLoop && stopAfterHandle) {
            //进入RunLoop时传入的参数,处理完事件就返回
            retVal = kCFRunLoopRunHandledSource;
        }else if (timeout_context->termTSR < mach_absolute_time()) {
            //RunLoop超时
            retVal = kCFRunLoopRunTimedOut;
        }else if (__CFRunLoopIsStopped(rl)) {
            //RunLoop被手动终止
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        }else if (rlm->_stopped) {
            //mode被终止
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        }else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            //mode中没有要处理的事件
            retVal = kCFRunLoopRunFinished;
        }
        //除了上面这几种情况,都继续循环
    } while (0 == retVal);
    return retVal;
}

可以发现:

  • 实际上 RunLoop 内部就是一个 do-while 循环
  • 当你调用 CFRunLoopRun() 时,线程就会一直停留在这个循环里,直到超时或被手动停止,该函数才会返回
  • 同时RunLoop有很多个mode,但是RunLoop在run的时候必须只能指定其中一个mode,运行起来之后,被指定的mode即为currentMode

RunLoop和线程的关系(总结)

  • RunLoop保存在一个全局的Dictionary里面,线程为key,RunLoop为Value

  • 线程刚创建的时候是没有RunLoop对象的,RunLoop会在第一次获取它的时候创建

  • RunLoop会在线程结束的时候销毁

  • 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop

  • 每条线程都有唯一的一个与之对应的RunLoop对象

  • 先有线程,再有RunLoop

参考文献

iOS开发-RunLoop详解
RunLoop从源码到应用全面解析
深入理解RunLoop

猜你喜欢

转载自blog.csdn.net/streamery/article/details/107714359