Files
knowledge-kit/Chapter1 - iOS/1.38.md
2025-12-30 21:07:15 +08:00

88 KiB
Raw Blame History

RunLoop 探究

为什么 main 函数可以保持一直运行而不退出?

卡顿如何监控

RunLoop 是什么

  • 运行循环
  • 在程序运行过程中循环做一些事情

作用:程序并不会马上退出,而是保持运行状态

  • 保持程序的持续运行

  • 处理App中的各种事件比如触摸事件、定时器事件等

  • 节省CPU资源提高程序性能该做事时做事该休息时休息。休息和工作是从用户态切换到内核态内核态切换到用户态的不断切换

  • ......

场景

  • 定时器Timer、PerformSelector

  • GCD Async Main Queue

  • 事件响应、手势识别、界面刷新

  • 网络请求

  • AutoreleasePool

先附上一张总结的非常棒的RunLoop图

一言以蔽之,什么是 RunLoop为什么 main 函数可以保持一直运行而不退出?

iOS 侧 main 函数中,调用 UIApplicationMain 方法,内部启动主线程的 RunLoopRunLoop 是一个事件循环的维护机制。有事情做的时候做事情Source0、Source1没有事做的时从用户态到内核态的切换去实现线程休眠。避免资源浪费。

RunLoop 几个重要角色

获取 RunLoop

iOS 中有2套 API 可以访问和使用 RunLoop。分别是

  • FoundationNSRunLoop

  • CoreFoundationCFRunLoopRef

//Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

//Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象

NSRunLoop 是对 CFRunLoopRef 的一层 OC 包装,所以要了解 RunLoop 的内部结果,就需要了解 CFRunLoopRef

  • RunLoop 保存在一个全局的 Dictionary 里,线程作为 keyRunLoop 作为 value

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

  • 主线程的 RunLoop 已经自动创建好了,子线程的 RunLoop 需要主动创建

  • RunLoop 在第一次获取时创建,在线程结束时消失

RunLoop 相关的5个类

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

CFRunLoopRef 是什么?

查看源码 CFRunLoop 发现是结构体对象别名,typedef struct __CFRunLoop * CFRunLoopRef;摘取主要信息如下

struct __CFRunLoop {
    pthread_t _pthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
};

其中 _modes 代表一个 RunLoop 有一个 set 存储运行模式,有多个 Mode。_currentMode 表示当前时刻只有一个 Mode。

CFRunLoopModeRef 是什么?查看发现

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    CFStringRef _name;
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
};

CFRunLoopModeRef 代表 RunLoop 的运行模式

  • 一个 RunLoop 包含若干个 Mode每个 Mode 包含若干个 Source/Timer/Observer
  • 每次 RunLoop 启动,只能指定一个 Mode这个 Mode 被叫做 CurrentMode
  • 如果需要切换 Mode只能退出 RunLoop则以一个 Mode 进入
  • 如果 Mode 里没有任何 Source0/Source1/Timer/ObserverRunLoop 会立马退出

系统默认注册了5个Mode

  • kCFRunLoopDefaultModeApp 的默认 Mode通常主线程是在这个 Mode 下运行
  • UITrackingRunLoopMode界面跟踪 Mode用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
  • UIInitializationRunLoopMode: 在刚启动 App 时进入的第一个 Mode启动完成后就不再使用
  • GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode通常用不到
  • kCFRunLoopCommonModes: 这是一个占位用的Mode不是一种真正的Mode

Demo:

结论NSRunLoop 是对 CFRunLoop 的一层包装。

QA为什么一个 RunLoop 需要创建这么多 Mode

这样做的目的是为了分隔开不同组的 Source/Timer/Observer 互不影响(性能、功能)。

在UITableView场景下不同的 RunLoop Mode 主要是与 UITableView 的滑动优化和事件处理相关的。当 UITableView 滑动时RunLoop的运行模式会从默认的 CFRunLoopDefaultMode 切换到 CFRunLoopTrackingMode此 Mode 下 RunLoop 主要关注于处理与滑动相关的触摸事件和动画效果,而忽略其他类型的事件,如定时器事件。这是因为如果同时处理所有类型的事件,可能会导致滑动不流畅,影响用户体验。之前添加到 CFRunLoopDefaultMode 上的事件通知如定时器事件可能无法被及时处理这就是为什么在UITableView 滑动时,添加到主线程的 NSTimer 可能会停止执行的原因。

Source0、Source1、Timer、Observers 是什么

struct __CFRunLoopMode {
    CFStringRef _name;
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
};

RunLoop 在各个 Mode 下做事情,其实就是在处理某个 Mode 中的 Source0、Source1、Timer、Observer 事件。

Source0:

  • 处理开发者主动提交的任务或应用内部逻辑。
    • performSelector:onThread:
    • dispatch_async到主线程的任务最终封装为Source0
    • 屏幕触摸事件处理(非基于 Port 的事件),对应需要手动触发的事件,对应官方文档 Input Source 中的 Custom 和 performSelector:onThread 事件源。UIKit控件的事件处理如按钮点击后的回调

Demo1给屏幕点击事件加断点查看堆栈可以看到是 Source0 触发的。

Demo2: - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait 该 API 的底层就是:向目标线程的 RunLoop 添加一个 Source0 事件,标记后唤醒线程执行对应的事件。

Source1:

  • 基于 Port 的线程间通信,可以主动唤醒 RunLoop
  • 用户触摸屏幕时,系统通过 Mach Port 将事件传递到应用主线程的 RunLoop。RunLoop 被 Source1 唤醒后,将事件分发给 Source0处理具体的 UI 响应逻辑(如 hitTest:withEvent: 和响应链)。
  • 字典。{machport : 1}

Timers

  • NSTimer

  • performSelector:withObject:afterDelay:,底层也是 Timer

Observers:

  • 用于监听 RunLoop 状态

  • UI刷新BeforeWaiting

  • AutoReleasePool 实现BeforeWaiting

CFRunLoopSourceRef 事件源(输入源)

早期的分法:

  • Ported-Based Source
  • Custom Input Source
  • Cocoa Perform Selector Source

现在的分法

  • Source0非基于 port 的,用户主动触发的事件
  • Source1: 基于 port的通过内核在线程间相互发送消息

一对多的关系

RunLoopTimer 的封装

  • + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
  • + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
  • - (void)performSelector:(SEL)aSelector withObject: (id)argument afterDelay: (NSTimeInterval)seconds inModes: (NSArray*)modes;
  • + (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
  • - (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;

CFRunLoopSource

Source 是 RunLoop 的数据源抽象类protocol

RunLoop 定义了2个 Version 的 Source

  • Source0处理 App 内部事件、App 自己负责管理(触发),如 UIEvent、CGSocket
  • Source1由 RunLoop 和内核管理Mach port 驱动,如 CFMachPort、CFMessagePort。

定义如下:

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;			/* immutable */
    CFMutableBagRef _runLoops;
    union {
	CFRunLoopSourceContext version0;	/* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;	/* immutable, except invalidation */
    } _context;
};

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);
    void	(*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
    void	(*perform)(void *info);
} 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);
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
    mach_port_t	(*getPort)(void *info);
    void *	(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
    void *	(*getPort)(void *info);
    void	(*perform)(void *info);
#endif
} CFRunLoopSourceContext1;

CFRunLoopObserver

向外部报告 RunLoop 当前状态的更改。框架中很多机制都是由 RunLoopObserver 触发,比如 CAAnimation、AutoReleasePool。

系统或者开发者很多都是 RunLoop 的业务方。

CFRunLoopMode

Mode 是 iOS App 滑动流畅的关键。

不同任务被添加到不同 Mode 中去。

UITrackingMode 模式下,核心关注滚动时 UI 流畅相关逻辑。

CFRunLoopObserverRef 监听 RunLoop 状态变化

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),                // 即将进入 RunLoop
    kCFRunLoopBeforeTimers = (1UL << 1),        // 即将处理 NSTimer
    kCFRunLoopBeforeSources = (1UL << 2),        // 即将处理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5),        // 即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),        // 刚从休眠中唤醒
    kCFRunLoopExit = (1UL << 7),                // 退出 RunLoop
    kCFRunLoopAllActivities = 0x0FFFFFFFU        
};

添加 Observer

//1、获得当前线程下的 RunLoop
CFRunLoopRef runloop = CFRunLoopGetCurrent();    
//2、为 RunLoop 创建观察者
CFRunLoopObserverRef obersver = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
});
//3、为当前的 RunLoop 添加观察者
CFRunLoopAddObserver(runloop, obersver, kCFRunLoopDefaultMode);
//4、在 CoreFoundation 框架中, create、copy、retain 过的对象都必须在最后 release
CFRelease(obersver);

注意CoreFoundation 的内存管理:凡是带有 Create、Copy、Retain 等字眼的函数创建出来的对象都需要在最后调用 release

//给 RunLoop 添加监听者
- (void) {

    //创建监听者
//  CFRunLoopObserverCreate(CFAllocatorGetDefault(), kCFRunLoopAllActivities, <#Boolean repeats#>, <#CFIndex order#>, <#CFRunLoopObserverCallBack callout#>, <#CFRunLoopObserverContext *context#>)
    /*
     创建监听对象
     参数1:分配内存空间
     参数2:要监听的状态 kCFRunLoopAllActivities :所有状态
     参数3:是否要持续监听
     参数4:优先级
     参数5:回调
     */

  CFRunLoopObserverRef oberver = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"RunLoop 闪亮登场");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"RunLoop 大哥要处理 Timer 了");
                break;
            case kCF RunLoopBeforeSources:
                //Source 有2种。Source0:非基于 port 的用户主动触发的事件。Source1:基于 port通过内核和其它线程互相发送消息
                NSLog(@"RunLoop 大哥要处理 Source 了");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"RunLoop 大哥没事干要睡觉了");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"");
                NSLog(@"RunLoop 大哥终于等到有缘人了,要醒来开始干活了");
                break;
            case kCFRunLoopExit:
                NSLog(@"RunLoop 大哥要退出离开了");
                break;
            default:
                break;
        }
    });
    /*
     参数1:要监听哪个RunLoop
     参数2:监听者
     参数3:要监听 RunLoop 在哪种运行模式下的状态
     */
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), oberver, kCFRunLoopDefaultMode);
    CFRelease(oberver);
    [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(wakeupRunLoop) userInfo:nil repeats:YES];
}


//等到 RunLoop 休眠后5秒钟叫醒 RunLoop
- (void)wakeupRunLoop{
    NSLog(@"%s",__func__);
}
/*
2018-08-01 11:23:49.401626+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Timer 了
2018-08-01 11:23:49.401950+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Source 了
2018-08-01 11:23:49.402326+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Timer 了
2018-08-01 11:23:49.402509+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Source 了
2018-08-01 11:23:49.402721+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Timer 了
2018-08-01 11:23:49.402855+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Source 了
2018-08-01 11:23:49.403080+0800 RunLoop[38148:1994974] RunLoop 大哥没事干要睡觉了
2018-08-01 11:23:49.459238+0800 RunLoop[38148:1994974] 
2018-08-01 11:23:49.459512+0800 RunLoop[38148:1994974] RunLoop 大哥终于等到有缘人了,要醒来开始干活了
2018-08-01 11:23:49.459740+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Timer 了
2018-08-01 11:23:49.459932+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Source 了
2018-08-01 11:23:49.460431+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Timer 了
2018-08-01 11:23:49.460607+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Source 了
2018-08-01 11:23:49.460775+0800 RunLoop[38148:1994974] RunLoop 大哥没事干要睡觉了
2018-08-01 11:23:49.880631+0800 RunLoop[38148:1994974] 
2018-08-01 11:23:49.880867+0800 RunLoop[38148:1994974] RunLoop 大哥终于等到有缘人了,要醒来开始干活了
2018-08-01 11:23:49.881530+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Timer 了
2018-08-01 11:23:49.881699+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Source 了
2018-08-01 11:23:49.881870+0800 RunLoop[38148:1994974] RunLoop 大哥没事干要睡觉了
2018-08-01 11:23:54.402263+0800 RunLoop[38148:1994974] 
2018-08-01 11:23:54.402562+0800 RunLoop[38148:1994974] RunLoop 大哥终于等到有缘人了,要醒来开始干活了
2018-08-01 11:23:54.402773+0800 RunLoop[38148:1994974] -[ViewController wakeupRunLoop]
2018-08-01 11:23:54.403081+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Timer 了
2018-08-01 11:23:54.403245+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Source 了
2018-08-01 11:23:54.403476+0800 RunLoop[38148:1994974] RunLoop 大哥没事干要睡觉了
2018-08-01 11:23:59.402151+0800 RunLoop[38148:1994974] 
2018-08-01 11:23:59.402511+0800 RunLoop[38148:1994974] RunLoop 大哥终于等到有缘人了,要醒来开始干活了
2018-08-01 11:23:59.402687+0800 RunLoop[38148:1994974] -[ViewController wakeupRunLoop]
2018-08-01 11:23:59.402913+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Timer 了
2018-08-01 11:23:59.403037+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Source 了
2018-08-01 11:23:59.403156+0800 RunLoop[38148:1994974] RunLoop 大哥没事干要睡觉了
*/

触摸屏幕事件在 RunLoop 下的 source0

上个实验是在主线程对 RunLoop 进行的监听,但是由于是主线程是由系统创建的,所以系统也创建了对应的主 RunLoop所以我们看不到 RunLoop 创建的状态,为了模拟完整的状态,我们开启子线程,在子线程中模拟

- (void)testRunLoopObserverOnSubThread{

    //创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("com.lbp.testRunLoopOnSubThread", DISPATCH_QUEUE_CONCURRENT);
    //开启子线程
    dispatch_async(queue, ^{

        //1、获得当前线程下的 RunLoop
        CFRunLoopRef runloop = CFRunLoopGetCurrent();


        //2、为 RunLoop 创建观察者
        CFRunLoopObserverRef obersver = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            switch (activity) {
                case kCFRunLoopEntry:
                    NSLog(@"RunLoop 闪亮登场");
                    break;
                case kCFRunLoopBeforeTimers:
                    NSLog(@"RunLoop 大哥要处理 Timer 了");
                    break;
                case kCFRunLoopBeforeSources:
                    //Source 有2种。Source0:非基于 port 的用户主动触发的事件。Source1:基于 port通过内核和其它线程互相发送消息
                    NSLog(@"RunLoop 大哥要处理 Source 了");
                    break;
                case kCFRunLoopBeforeWaiting:
                    NSLog(@"RunLoop 大哥没事干要睡觉了");
                    break;
                case kCFRunLoopAfterWaiting:
                    NSLog(@"");
                    NSLog(@"RunLoop 大哥终于等到有缘人了,要醒来开始干活了");
                    break;
                case kCFRunLoopExit:
                    NSLog(@"RunLoop 大哥要退出离开了");
                    break;
                default:
                    break;
            }
        });
        //为了运行 RunLoop 必须触发事件
        [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(wakeUpRunLoopOnSubThread) userInfo:nil repeats:NO];
        //3、为当前的 RunLoop 添加观察者
        CFRunLoopAddObserver(runloop, obersver, kCFRunLoopDefaultMode);
        //4、在 CoreFoundation 框架中, create、copy、retain 过的对象都必须在最后 release
        CFRelease(obersver);
        //5、在非主线程创建的 RunLoop 必须触发运行
        [[NSRunLoop currentRunLoop] run];        
    });
}


- (void)wakeUpRunLoopOnSubThread{
    NSLog(@"%s",__func__);
}
/*
2018-08-01 14:23:06.453282+0800 RunLoop[2376:115968] RunLoop 闪亮登场
2018-08-01 14:23:06.453608+0800 RunLoop[2376:115968] RunLoop 大哥要处理 Timer 了
2018-08-01 14:23:06.453781+0800 RunLoop[2376:115968] RunLoop 大哥要处理 Source 了
2018-08-01 14:23:06.453982+0800 RunLoop[2376:115968] RunLoop 大哥没事干要睡觉了
2018-08-01 14:23:08.458237+0800 RunLoop[2376:115968] 
2018-08-01 14:23:08.458658+0800 RunLoop[2376:115968] RunLoop 大哥终于等到有缘人了,要醒来开始干活了
2018-08-01 14:23:08.458894+0800 RunLoop[2376:115968] -[ViewController wakeUpRunLoopOnSubThread]
2018-08-01 14:23:08.459082+0800 RunLoop[2376:115968] RunLoop 大哥要退出离开了
*/

RunLoop 运行原理

运行原概要

RunLoop 运行原理图1

  • 图上左上角的 Input source 是早期 RunLoop 的分法现在分法为Source0 和 Source1。
    • Source0:非基于 port 的,用户主动触发的事件。
    • Source1:基于 port通过内核和其它线程互相发送消息
  • RunLoop 我们不能自己手动创建,而是可以通过 [NSRunLoop currentRunLoop] 方法获取,类似于懒加载。系统底层的做法是在全局维护了一个字典,字典的 key 和 value 分别是当前的线程和线程对应的 RunLoop如果新开辟的线程没有对应的 RunLoop系统则为其创建 RunLoop并将其写入字典线程、为其创建的 RunLoop

源码探究

内部就是 do-while 的循环在这个循环内部不断处理各种任务Timer、Source、Observer

我们来看看苹果官方开源的 CFRunLoop.c 文件。看几个关键函数的实现猜测下 RunLoop 的内部原理

但是如何直到系统是运行 RunLoop 的哪个函数?给 viewDidLoad 设置断点,在 lldb 模式输入 bt 查看堆栈

查看 CF 中 CFRunLoop.c源码。方法比较复杂,做了精简摘要

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    // ...
    // 通知 Observers 进入 RunLoop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    //  RunLoop 运行循环主逻辑
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    // 通知 Observers 退出 RunLoop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    return result;
}

CFRunLoopRunSpecific 方法就是系统启动 RunLoop 的入口。内部先通知 Runloop 的观察者进入 Runloop 了,然后 调用 __CFRunLoopRun 执行核心逻辑(处理 timers、source 事件、block最后告诉观察者退出 Runloop。

我们继续看看 __CFRunLoopRun 。源码很多很乱,对无关代码进行裁剪,便于理解流程逻辑

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    int32_t retVal = 0;
    do {
        // 通知 Obserers即将处理 Timers
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        // 通知 Obserers即将处理 Sources
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        // 处理 blocks
        __CFRunLoopDoBlocks(rl, rlm);
        // 处理 Source0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            // 处理 blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }

        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        // 判断有无 Source1
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
            msg = (mach_msg_header_t *)msg_buffer;
            // 如果有 Source1 则跳转到 handle_msg
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                goto handle_msg;
            }
        }

        didDispatchPortLastTime = false;
        // 通知 Observers即将休眠
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);

        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();


        do {
            if (kCFUseCollectableAllocator) {
                // objc_clear_stack(0);
                // <rdar://problem/16393959>
                memset(msg_buffer, 0, sizeof(msg_buffer));
            }
            msg = (mach_msg_header_t *)msg_buffer;
            // 等待其他消息来唤醒 RunLoop
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

            if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                // Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
                while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
                if (rlm->_timerFired) {
                    // Leave livePort as the queue port, and service timers below
                    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);
            // 通知 Observers结束休眠
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
	
     handle_msg:;	// 判断是怎么唤醒的 Runloop
        __CFRunLoopSetIgnoreWakeUps(rl);
				// 
        if (MACH_PORT_NULL == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
        } else if (livePort == rl->_wakeUpPort) {
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
        }
        // 被 Timer 唤醒,执行代码。
        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);
            }
        }
				// 被 Timer 唤醒,执行代码。
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            // On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
            // In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
        // 被 GCD 唤醒
        else if (livePort == dispatchPort) {
            // 处理 GCD
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else {
            // 被 Source1 唤醒
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            // 处理 Source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;

        }
        if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
        // 处理 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;
    }

        voucher_mach_msg_revert(voucherState);
        os_release(voucherCopy);
    } while (0 == retVal);	// 当 retVal == 0 的时候结束 Runloop
    return retVal;
}

另一个版本

  /* rl, rlm are locked on entrance and exit */
  static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {


      uint64_t startTSR = mach_absolute_time();


      if (__CFRunLoopIsStopped(rl)) {
          __CFRunLoopUnsetStopped(rl);
      return kCFRunLoopRunStopped;
      } else if (rlm->_stopped) {
      rlm->_stopped = false;
      return kCFRunLoopRunStopped;
      }


      mach_port_name_t dispatchPort = MACH_PORT_NULL;
      Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
      if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();


  #if USE_DISPATCH_SOURCE_FOR_TIMERS
      mach_port_name_t modeQueuePort = MACH_PORT_NULL;
      if (rlm->_queue) {
          modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
          if (!modeQueuePort) {
              CRASH("Unable to get port for run loop mode queue (%d)", -1);
          }
      }
  #endif


      dispatch_source_t timeout_timer = NULL;
      struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
      if (seconds <= 0.0) { // instant timeout
          seconds = 0.0;
          timeout_context->termTSR = 0ULL;
      } else if (seconds <= TIMER_INTERVAL_LIMIT) {
      dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
      timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
          dispatch_retain(timeout_timer);
      timeout_context->ds = timeout_timer;
      timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
      timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
      dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
      dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
          dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
          uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
          dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
          dispatch_resume(timeout_timer);
      } else {
          // 设置RunLoop超时时间
          seconds = 9999999999.0;
          timeout_context->termTSR = UINT64_MAX;
      }


      Boolean didDispatchPortLastTime = true;
      int32_t retVal = 0;
      do {
  #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
          voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
          voucher_t voucherCopy = NULL;
  #endif
          uint8_t msg_buffer[3 * 1024];
  #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
          mach_msg_header_t *msg = NULL;
          mach_port_t livePort = MACH_PORT_NULL;
  #elif DEPLOYMENT_TARGET_WINDOWS
          HANDLE livePort = NULL;
          Boolean windowsMessageReceived = false;
  #endif
      __CFPortSet waitSet = rlm->_portSet;


          __CFRunLoopUnsetIgnoreWakeUps(rl);


          if (rlm->_observerMask & kCFRunLoopBeforeTimers)
              // 2. 通知 Observers: RunLoop 即将触发 Timer 回调
              __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
          if (rlm->_observerMask & kCFRunLoopBeforeSources)
              // 3. 通知 Observers: RunLoop 即将触发 Source 回调
              __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
          // 执行被加入的block
      __CFRunLoopDoBlocks(rl, rlm);


          // 4. RunLoop 触发 Source0 (非port) 回调
          Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
          if (sourceHandledThisLoop) {
              // 执行被加入的block
              __CFRunLoopDoBlocks(rl, rlm);
      }


          Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);


          // 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息
          if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
  #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
              msg = (mach_msg_header_t *)msg_buffer;


              if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                  goto handle_msg;
              }
  #elif DEPLOYMENT_TARGET_WINDOWS
              if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
                  goto handle_msg;
              }
  #endif
          }


          didDispatchPortLastTime = false;


      // 通知 Observers: RunLoop 的线程即将进入休眠(sleep)
      if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
      __CFRunLoopSetSleeping(rl);
      // do not do any user callouts after this point (after notifying of sleeping)


          // Must push the local-to-this-activation ports in on every loop
          // iteration, as this mode could be run re-entrantly and we don't
          // want these ports to get serviced.


          __CFPortSetInsert(dispatchPort, waitSet);


      __CFRunLoopModeUnlock(rlm);
      __CFRunLoopUnlock(rl);


          CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();


  #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
  #if USE_DISPATCH_SOURCE_FOR_TIMERS
          do {
              if (kCFUseCollectableAllocator) {
                  // objc_clear_stack(0);
                  // <rdar://problem/16393959>
                  memset(msg_buffer, 0, sizeof(msg_buffer));
              }
              msg = (mach_msg_header_t *)msg_buffer;


              __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);


              if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                  // Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
                  while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
                  if (rlm->_timerFired) {
                      // Leave livePort as the queue port, and service timers below
                      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);
  #else
          if (kCFUseCollectableAllocator) {
              // objc_clear_stack(0);
              // <rdar://problem/16393959>
              memset(msg_buffer, 0, sizeof(msg_buffer));
          }
          msg = (mach_msg_header_t *)msg_buffer;
          __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
  #endif




  #elif DEPLOYMENT_TARGET_WINDOWS
          // Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages.
          __CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
  #endif


          __CFRunLoopLock(rl);
          __CFRunLoopModeLock(rlm);


          rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));


          // Must remove the local-to-this-activation ports in on every loop
          // iteration, as this mode could be run re-entrantly and we don't
          // want these ports to get serviced. Also, we don't want them left
          // in there if this function returns.


          __CFPortSetRemove(dispatchPort, waitSet);


          __CFRunLoopSetIgnoreWakeUps(rl);


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


          // 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了
      if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
          // 处理消息
          handle_msg:;
          __CFRunLoopSetIgnoreWakeUps(rl);


  #if DEPLOYMENT_TARGET_WINDOWS
          if (windowsMessageReceived) {
              // These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after
              __CFRunLoopModeUnlock(rlm);
          __CFRunLoopUnlock(rl);


              if (rlm->_msgPump) {
                  rlm->_msgPump();
              } else {
                  MSG msg;
                  if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) {
                      TranslateMessage(&msg);
                      DispatchMessage(&msg);
                  }
              }


              __CFRunLoopLock(rl);
          __CFRunLoopModeLock(rlm);
           sourceHandledThisLoop = true;


              // To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced
              // Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later.
              // NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling.
              __CFRunLoopSetSleeping(rl);
              __CFRunLoopModeUnlock(rlm);
              __CFRunLoopUnlock(rl);


              __CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL);


              __CFRunLoopLock(rl);
              __CFRunLoopModeLock(rlm);            
              __CFRunLoopUnsetSleeping(rl);
              // If we have a new live port then it will be handled below as normal
          }




  #endif
          if (MACH_PORT_NULL == livePort) {
              CFRUNLOOP_WAKEUP_FOR_NOTHING();
              // handle nothing
          } else if (livePort == rl->_wakeUpPort) {
              CFRUNLOOP_WAKEUP_FOR_WAKEUP();
              // do nothing on Mac OS
  #if DEPLOYMENT_TARGET_WINDOWS
              // Always reset the wake up port, or risk spinning forever
              ResetEvent(rl->_wakeUpPort);
  #endif
          }
  #if USE_DISPATCH_SOURCE_FOR_TIMERS
          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);
              }
          }
  #endif
  #if USE_MK_TIMER_TOO
          // 9.1 如果一个 Timer 到时间了触发这个Timer的回调
          else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
              CFRUNLOOP_WAKEUP_FOR_TIMER();
              // On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
              // In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
              if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                  // Re-arm the next timer
                  __CFArmNextTimerInMode(rlm, rl);
              }
          }
  #endif
          // 9.2 如果有dispatch到main_queue的block执行block
          else if (livePort == dispatchPort) {
              CFRUNLOOP_WAKEUP_FOR_DISPATCH();
              __CFRunLoopModeUnlock(rlm);
              __CFRunLoopUnlock(rl);
              _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
  #if DEPLOYMENT_TARGET_WINDOWS
              void *msg = 0;
  #endif
              /**/
              __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
              _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
              __CFRunLoopLock(rl);
              __CFRunLoopModeLock(rlm);
              sourceHandledThisLoop = true;
              didDispatchPortLastTime = true;
          }
          // 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
          else {
              CFRUNLOOP_WAKEUP_FOR_SOURCE();


              // If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD. CFMachPortBoost will look in the TSD for the voucher. By using the value in the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again.
              voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);


              /**/
              CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
              if (rls) {
  #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
          mach_msg_header_t *reply = NULL;
          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);
          }
  #elif DEPLOYMENT_TARGET_WINDOWS
                  sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
  #endif
          }


              // Restore the previous voucher
              _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);


          } 
  #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
          if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
  #endif
      // 执行加入到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一个都没有
          retVal = kCFRunLoopRunFinished;
      }


  #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
          voucher_mach_msg_revert(voucherState);
          os_release(voucherCopy);
  #endif
      // 如果没超时mode里没空loop也没被停止那继续loop
      } while (0 == retVal);


      if (timeout_timer) {
          dispatch_source_cancel(timeout_timer);
          dispatch_release(timeout_timer);
      } else {
          free(timeout_context);
      }

      return retVal;
  }

__CFRunLoopModeIsEmpty函数的作用就是判断这个 Mode 下面有没有 source0、source1、timer只要存在就说明当前 Mode 不是空的,同时看看这个 Mode 是不是属于当前的 RunLoop

// expects rl and rlm locked
  static Boolean __CFRunLoopModeIsEmpty(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopModeRef previousMode) {
      CHECK_FOR_FORK();
      if (NULL == rlm) return true;
  #if DEPLOYMENT_TARGET_WINDOWS
      if (0 != rlm->_msgQMask) return false;
  #endif
      Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
      if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) return false; // represents the libdispatch main queue
      // 判断时候有没有_sources0
      if (NULL != rlm->_sources0 && 0 < CFSetGetCount(rlm->_sources0)) return false;
      // 判断时候有没有_sources1
      if (NULL != rlm->_sources1 && 0 < CFSetGetCount(rlm->_sources1)) return false;
      // 判断时候有没有_timers
      if (NULL != rlm->_timers && 0 < CFArrayGetCount(rlm->_timers)) return false;


      struct _block_item *item = rl->_blocks_head;
      while (item) {
          struct _block_item *curr = item;
          item = item->_next;
          Boolean doit = false;
          if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
              doit = CFEqual(curr->_mode, rlm->_name) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(rl->_commonModes, rlm->_name));
          } else {
              doit = CFSetContainsValue((CFSetRef)curr->_mode, rlm->_name) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(rl->_commonModes, rlm->_name));
          }
          if (doit) return false;
      }
      return true;
  }

CFRunLoopRun、CFRunLoopRunInMode

1、2个函数的作用分别是让 RunLoop 跑在 KCFRunLoopDefaultMode 下和特定的 Mode 下

2、2个函数本质上都是调用 CFRunLoopRunSpecific

  // 用DefaultMode启动
  void CFRunLoopRun(void) {    /* DOES CALLOUT */
      int32_t result;
      do {
          result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
          CHECK_FOR_FORK();
      } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
  }


  // 用指定的Mode启动允许设置RunLoop超时时间
  SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
      CHECK_FOR_FORK();
      return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
  }

RunLoop 内部几个核心的动作:__CFRunLoopDoObservers__CFRunLoopServiceMachPort__CFRunLoopDoTimers 方法内部实现调用的还是 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION____CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ 等方法,均以 __CFRUNLOOP_IS_CALLING_OUT_TO_ 方法名作为开头。可以在堆栈上得以体现。

运行流程

上面结合源码看了 Runloop 是怎么运行的。下面通过图片看看 RunLoop 各个状态运行切换的完整流程。

Demo

  1. 上面第4步的 blocks 是指可以给 RunLoop 添加 Block 任务。

    CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopDefaultMode, ^{
        NSLog(@"runloop block task");
    });
    
  2. 上面8>2Runloop 处理 GCD Async To Main Quque

RunLoop 休眠原理

Runloop 在处理完 timer、source、block 后会检查有没有 source1 事件,没有则休眠。这个休眠是 while 循环死等吗?怎么实现的?

可以在 App 运行过程中,点击 Xcode 左下角的 debug 暂停按钮,可以看到 App 堆栈存在系统调用

本质上就是函数 __CFRunLoopServiceMachPort   来控制实现休眠。查看 CFRunLoop.c 源码可以发现,是由 mach_msg 实现的。不是平时的在应用层 API 上 sleep 休眠的。比如 while 循环。

mach_msg 休眠是从用户态切换到内核态,内核 api 控制休眠,做到真正节省资源的作用,等到由新消息来到,继续切换到用户态。能力更底层,效果更好,从而更加省电

static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout, voucher_mach_msg_state_t *voucherState, voucher_t *voucherCopy) {
    Boolean originalBuffer = true;
    kern_return_t ret = KERN_SUCCESS;
    for (;;) {        /* In that sleep of death what nightmares may come ... */
        mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
        msg->msgh_bits = 0;
        msg->msgh_local_port = port;
        msg->msgh_remote_port = MACH_PORT_NULL;
        msg->msgh_size = buffer_size;
        msg->msgh_id = 0;
        if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
        ret = mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);

        // Take care of all voucher-related work right after mach_msg.
        // If we don't release the previous voucher we're going to leak it.
        voucher_mach_msg_revert(*voucherState);

        // Someone will be responsible for calling voucher_mach_msg_revert. This call makes the received voucher the current one.
        *voucherState = voucher_mach_msg_adopt(msg);

        if (voucherCopy) {
            if (*voucherState != VOUCHER_MACH_MSG_STATE_UNCHANGED) {
                // Caller requested a copy of the voucher at this point. By doing this right next to mach_msg we make sure that no voucher has been set in between the return of mach_msg and the use of the voucher copy.
                // CFMachPortBoost uses the voucher to drop importance explicitly. However, we want to make sure we only drop importance for a new voucher (not unchanged), so we only set the TSD when the voucher is not state_unchanged.
                *voucherCopy = voucher_copy();
            } else {
                *voucherCopy = NULL;
            }
        }

        CFRUNLOOP_WAKEUP(ret);
        if (MACH_MSG_SUCCESS == ret) {
            *livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
            return true;
        }
        if (MACH_RCV_TIMED_OUT == ret) {
            if (!originalBuffer) free(msg);
            *buffer = NULL;
            *livePort = MACH_PORT_NULL;
            return false;
        }
        if (MACH_RCV_TOO_LARGE != ret) break;
        buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
        if (originalBuffer) *buffer = NULL;
        originalBuffer = false;
        *buffer = realloc(*buffer, buffer_size);
    }
    HALT;
    return false;
}

RunLoop 是如何响应用户操作的?

用户交互事件首先在 IOHID 层生成 HIDEvent然后向事件处理线程的 Source1 的 mach port 发送 HIDEvent 消息Source1 的回调函数将事件转化为 UIEvent 并筛选需要处理的事件推入待处理事件队列,向主线程的事件处理 Source0 发送信号,并唤醒主线程,主线程检查到事件处理 Source0 有待处理信号后,触发 Source0 的回调函数,从待处理事件队列中提取 UIEvent最后进入 hit-test 等 UIEvent 事件响应流程

等待梳理完善。

CFRunLoopTimerRef 是基于时间的触发器

  • 基本上说就是 NSTimer它会收到 RunLoopMode 的影响
  • GCD 的 timer 不受 RunLoopMode 的影响

源码解读

  • performSelector 是在 source0 上实现的
  • RunLoopTimer 外部接口设置的精度精度大于0则使用 dispatch_source_set_timer精度小于0则使用 mk_timer_arm
  • timer_source 使用 dispatch_timer_STRICT 创建,则系统会尽最大努力遵守设置的 leeway 值
  • NSTimer 不准的原因:底层 RunLoop Timer 底层使用的 timer 的精度不高mk_timer与 RunLoop 底层的调用机制有关系
  • 那么为什么存在 RunLoopTimer意义是什么应用场景
// Data structure to hold TSD data, cleanup functions for each
typedef struct __CFTSDTable {
    uint32_t destructorCount;
    uintptr_t data[CF_TSD_MAX_SLOTS];
    tsdDestructor destructors[CF_TSD_MAX_SLOTS];
} __CFTSDTable;
// 主线程 RunLoop
CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    // 局部静态变量
    static CFRunLoopRef __main = NULL; // no retain needed
    // 创建主线程对应的 RunLoop
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}

// 子线程 RunLoop
// 先从 __CFTSDTable 获取 RunLoop如果有则 return没有则调用 _CFRunLoopGet0_CFRunLoopGet0 内部调用 __CFRunLoopCreate 创建 CFRunLoop然后写入 __CFTSDTable
CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    // __CFTSDTable
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    // 没有则创建
    return _CFRunLoopGet0(pthread_self());
}

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(_CFThreadRef t) {
    if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {
        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);
    }
    CFRunLoopRef newLoop = NULL;
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
        newLoop = __CFRunLoopCreate(t);
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
    __CFUnlock(&loopsLock);
    // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
    if (newLoop) { CFRelease(newLoop); }

    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
#if _POSIX_THREADS
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
#else
            _CFSetTSD(__CFTSDKeyRunLoopCntr, 0, &__CFFinalizeRunLoop);
#endif
        }
    }
    return loop;
}

为什么只有当 RunLoop 中存在 Timer Sourcrs、Input Sources 时,才能保证 RunLoop 不退出?

RunLoop 本质就是一个有条件的 do...while 循环。__CFRunLoopModeIsEmpty 里面去判断 source0、source1、timers 不存在则 while 循环条件不满足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);
}

// expects rl and rlm locked
static Boolean __CFRunLoopModeIsEmpty(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopModeRef previousMode) {
    // ...
    if (NULL != rlm->_sources0 && 0 < CFSetGetCount(rlm->_sources0)) return false;
    if (NULL != rlm->_sources1 && 0 < CFSetGetCount(rlm->_sources1)) return false;
    if (NULL != rlm->_timers && 0 < CFArrayGetCount(rlm->_timers)) return false;
    // ...
    return true;
}
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
// ...
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
    Boolean did = false;
    if (currentMode) __CFRunLoopModeUnlock(currentMode);
    __CFRunLoopUnlock(rl);
    return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
// ...

Mach Port 跨线程通信

  1. Mach IPC 基于 Mach 内核实现进程间通讯。

  2. Mach IPC 被抽象为3种操作messages、ports、and port sets

  3. Mach port 跨线程通信

    线程 B 有个 port 在等待消息,具有消息接收权限,等待消息到来的时候会阻塞当前线程

    线程 A 有个 port 在发送消息具有发送消息权限要把发送的消息包装成消息通过消息队列传递message 包括header目的地 port、size、data。

    线程 B 收到消息后,解除 block线程继续向下运行

  4. Mach port 如何进行跨线程通信?

    线程开启一个 port然后给 port 申请接收、发送的权限。mach_msg 是通信函数,在等待消息的时候不加 timeout 则会一直阻塞,等到消息到来

  5. 在测试工作中 main.m 文件中打印当前的 RunLoop

    int main(int argc, char * argv[]) {
     NSString * appDelegateClassName;
     @autoreleasepool {
         appDelegateClassName = NSStringFromClass([AppDelegate class]);
     }
     NSLog(@"%@", NSRunLoop.currentRunLoop);
     return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    
    // 
    2020-08-13 09:49:41.326621+0800 ***[52423:2383402] <CFRunLoop 0x6000015f0000 [0x7fff8062d750]>{wakeup port = 0x1103, stopped = false, ignoreWakeUps = true,
     current mode = (none),
     common modes = <CFBasicHash 0x6000027f1320 [0x7fff8062d750]>{type = mutable set, count = 1,
         entries =>
         2 : <CFString 0x7fff80640a20 [0x7fff8062d750]>{contents = "kCFRunLoopDefaultMode"}
     }
     ,
     common mode items = (null),
     modes = <CFBasicHash 0x6000027f1290 [0x7fff8062d750]>{type = mutable set, count = 1,
         entries =>
         2 : <CFRunLoopMode 0x6000012fc0d0 [0x7fff8062d750]>{name = kCFRunLoopDefaultMode, port set = 0x1003, queue = 0x6000007f0100, source = 0x6000007f0280 (not fired), timer port = 0xe03,
             sources0 = (null),
             sources1 = (null),
             observers = (null),
             timers = (null),
             currently 618976181 (257342842892563) / soft deadline in: 1.84464867e+10 sec (@ -1) / hard deadline in: 1.84464867e+10 sec (@ -1)
         },
     }
    }
    

    可以看到 main.m 中,还没有 return 的时候当前 RunLoop 的内部结构中存在一个 wakeup port 的端口。查看 RunLoop 源代码 wakeup port 就是 mach port 的一种。

    typedef mach_port_t __CFPort;
    
    struct __CFRunLoop {
     CFRuntimeBase _base;
     _CFRecursiveMutex _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
     _CFThreadRef _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;
     _Atomic(uint8_t) _fromTSD;
     CFLock_t _timerTSRLock;
    };
    
    void CFRunLoopWakeUp(CFRunLoopRef rl) {
     // ...
     kern_return_t ret;
     /* We unconditionally try to send the message, since we don't want
      * to lose a wakeup, but the send may fail if there is already a
      * wakeup pending, since the queue length is 1. */
     ret = __CFSendTrivialMachMessage(rl->_wakeUpPort, 0, MACH_SEND_TIMEOUT, 0);            
     // ...
     int ret;
     do {
         ret = eventfd_write(rl->_wakeUpPort, 1);
     } while (ret == -1 && errno == EINTR);
     // ...
     SetEvent(rl->_wakeUpPort);
     // ...
    }
    

    CFRunLoopWakeUp(CFRunLoopGetCurrent()); 可以唤醒 RunLoop函数的底层实现如上。核心实现就是 __CFSendTrivialMachMessage 函数。在iOS 中,除了 source1 可以自己唤醒 RunLoop 之外,其他的事件都需要用户手动唤醒 RunLoop 才可以。RunLoop 提供了专门的方法来实现这个功能。其核心部分就是调用 mach_msg 来向指定的 _wakeUpPort 端口发送消息,从而唤醒线程继续工作。

    为什么 Source1 可以唤醒 RunLoop因为 Source1 本质上就是针对 Mach Port 的封装

  6. 做个实验检测 _wakeUpPort 端口

    - (void)viewDidLoad {
      [super viewDidLoad];
      [self listenWakeUpPort];
    }
    
    - (void)listenWakeUpPort
    {
      NSArray<NSString *> *array = [NSRunLoop.currentRunLoop.description componentsSeparatedByString:@"wakeup port = "];
      NSString *wakeupPort = [array.lastObject substringToIndex:[array.lastObject rangeOfString:@","].location];
    
      dispatch_queue_t queue = dispatch_queue_create("com.test.wake_up_port_queue", DISPATCH_QUEUE_CONCURRENT);
      dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, [self numberWithHexString:wakeupPort], 0, queue);
      dispatch_source_set_event_handler(source, ^{
          mach_port_t port = (mach_port_t)dispatch_source_get_handle(source);
          NSLog(@"%u--wakeUp", port);
      });
      dispatch_activate(source);
    }
    
    - (NSInteger)numberWithHexString:(NSString *)hexString
    {
      const char *hexChar = [hexString cStringUsingEncoding:NSUTF8StringEncoding];
      int hexNumber;
      sscanf(hexChar, "%x", &hexNumber);
      return (NSInteger)hexNumber;
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
      CFRunLoopWakeUp(CFRunLoopGetCurrent());
    }
    

    可以看到每次在点击屏幕时调用 CFRunLoopWakeUp 尝试唤醒 RunLoop然后监听 RunLoop 的 _wakeUpPort都可以在回调中获取到消息。

RunLoop 应用场景

  • 控制线程生命周期(线程保活)

  • 解决 NSTimer 在滑动时停止工作的问题

  • APM 卡顿监控

  • 性能优化

NSTimer 经常会不准确,原因是什么?

NSTimer 在创建的时候经常会指派到特定的 NSRunLoopMode 中去举个例子默认创建的NSTimer 是被添加到 NSRunLoopDefaultMode 中去,当你的页面上有 UIScrollView 或者子类的时如果被拖动了,当前 RunLoop 的 NSRunloopMode 会从 NSDefaultRunLoopMode 转变为 UITrackingRunLoopMode 。遇到这种情况你需要精确的 NSTimer 的话,在创建好 NSTimer 之后,设置 RunLoopMod 为 NSRunLoopCommonModes。

注意NSRunLoopCommonModes 只是一个标识而已,而不是具体的模式。

[NSRunLoop currentRunLoop] addTimer:forMode: 的作用是告诉 RunLoop 当前 Timer 是可以在 NSRunLoopCommonModes 这个标识的 Mode 下运行。

UITrackingRunLoopMode、NSDefaultRunLoopMode 都是属于 NSRunLoopCommonModes 这个标识的。

NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(show) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

NSTimer 会受 NSRunLoopMode 影响GCD 的 timer 则不会。

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) dispatch_source_t timer;
@end


@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    /*
     只在默认状态下执行的 NSTimer
    [NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"我在执行了");
    }];
     */

    /*
     指定 NSRunLoopMode 的 NSTimer
    NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(show) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
     */

    /*
     GCD 的单位是 纳秒.
     使用 GCD 创建的 timer 正常创建后不会执行因为创建后设置了指定的时间后触发所以当代码运行到最后一行的时候Timer 还没执行,就被销毁了。所以我们必须设置一个属性去保存它。
     */
    //1、创建队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    //2、创建 timer
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    self.timer = timer;
    //3、设置 timer 的参数:精准度、时间间隔
    //第三个参数为 GCD timer 的精准度
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    //4、为 Timer 设置任务
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"%@",[NSRunLoop currentRunLoop]);
    });
    //5、执行任务
    dispatch_resume(timer);
}

- (void)show{
    NSLog(@"shw-%@",[NSThread currentThread]);
    NSLog(@"%@",[NSRunLoop currentRunLoop]);
}
@end

ImageView显示(PerformSelector)

UITableView 在滚动的时候一个优化点之一就是 UIImageView 的显示,通常需要根据网络去下载图片。所以如果用户快速滚动列表的时候,如果立马下载并显示图片的话,势必会对 UI 的刷新产生影响直观的表现就是会卡顿FPS 达不到60。

- (void)downloadAndShowImage{
    self.imageview.image = [UIImage imageNamed:@"test"];
}

- (IBAction)clickLoadIMage:(id)sender {
    //[self.imageview performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"test"] afterDelay:2];
    [self performSelector:@selector(downloadAndShowImage) withObject:nil afterDelay:2 inModes:@[NSDefaultRunLoopMode,UITrackingRunLoopMode]];
}

知道 RunLoop 的工作原理,就清楚 UITableView任何 UIScrollView 子类在滚动的时候RunLoop 会处于 UITrackingRunLoopMode,那么可以将图片下载或者解码显示的逻辑放到 NSDefaultRunLoopMode

[self performSelector:@selector(downloadAndShowImage) withObject:nil afterDelay:0 inModes:@[NSDefaultRunLoopMode]];

自动释放池

自动释放池什么时候创建和释放?

App 启动后,主线程 RunLoop 里注册了2个 Observer其回调都是 _wrapRunLoopWithAutoReleasePoolHandler

第一个 Observer 监听 RunLoop 的 kCFRunLoopEntry 状态(即将进入 RunLoop回调为 _objc_autoreleasePoolPush(),会创建自动释放池,其 order 为 -2147483647,优先级最高,保证创建自动释放池一定是发生在其他回调之前。

第二个 Observer 监听 RunLoop 2个事件

  • 监听 kCFRunLoopBeforeWaiting (将要休眠),回调为 _objc_autoreleasePoolPop()_objc_autoreleasePoolPush(),用来释放旧的自动释放池,创建新的自动释放池。

  • 监听 kCFRunLoopExit(RunLoop 即将退出),回调为 _objc_autoreleasePoolPop()Observer 优先级为 2147483647优先级最低保证自动释放池的释放在其他所有的回调之后进行。

总结版在主线程执行的代码通常是写在事件回调、Timer 回调内的,这些回调都会被 RunLoop 自身状态相关的 AutoreleasePool 所包裹,所以会自动管理内存,开发者不需要手动创建 AutoreleasePool。

事件响应

系统注册了 Source1基于 Mach port用来接收系统事件其回调函数为 __IOHIDEventSystemClientQueueCallback

当一个硬件事件(触摸/锁屏/摇晃等)发生时,首先 IOKit.Framework 会生成一个 IOHIDEvent 事件并由 SpringBoard 接收。SpringBoard 只接收按键、触摸、传感器等几种 Event然后通过 Mach Port 转发给需要处理的 App 进程。随后苹果注册的 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。

_UIApplicationHandleEventQueue 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理和分发(其中包括 UIGesture、屏幕旋转等

手势识别

_UIApplicationHandleEventQueue 识别到一个手势时,首先会调用 cancel 将当前的 touchBegin/End/Move 系统回调打断,然后系统会将对应的 UIGestureRecognizer 标记为待处理。

苹果注册了一个 Observer 监控 RunLoop 的 kCFRunLoopBeforeWaiting(将要休眠)状态,回调为 _UIGestureRecognizerUpdateObserver,其内部会获取所有刚被标记为待处理的 UIGestureRecognizer并执行对应的回调。

UI 刷新

当界面的 Frame 改变,或者更改 UIView、CALayer 的层次时,或者调用了 UIView、CALayer 的 setNeedsLayout、setNeedsDisplay 方法后,这个 UIView、CALayer 会被标记为待处理(类比前端的 Virtual Dom Diff标记为 dirty并被提交到一个全局容器中。

苹果设计 UI 更新也是 RunLoop 的业务方,所以会注册一个 Observer 监控 kCFRunLoopBeforeWaiting(将要休眠)和 kCFRunLoopExit (即将退出 RunLoop)状态,然后会执行 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv() 回调。内部会遍历所有待处理的 UIView、CALayer 以执行实际的绘制和渲染,更新 UI

RunLoop 空闲时做一些任务

- (void)print
{
    NSLog(@"test");
}

- (void)viewDidLoad
{
    CFRunLoopActivity flags = kCFRunLoopBeforeWaiting;
    CFRunLoopObserverRef runloopObserver = CFRunLoopObserverCreateWithHandler(
        kCFAllocatorDefault, flags, YES, 0,
        ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            [self performSelector:@selector(print)
                         onThread:[NSThread mainThread]
                       withObject:nil
                    waitUntilDone:NO
                            modes:@[ NSDefaultRunLoopMode ]];
        });
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), runloopObserver, kCFRunLoopDefaultMode);     
}

Crash 防护

利用监控手段,比如 C/OC crash、Signal、Mach 异常,当监控到异常之后,正常来说会发生闪退等,体验较差。某些场景下希望 App 从异常中恢复,重新启动,这个可以利用 RunLoop 实现。

CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (1) {
    for (NSString *mode in (__bridge NSArray *)allModes) {
        if ([mode isEqualToString:(NSString *)kCFRunLoopCommonModes]) {
          continue;
        }
        CFStringRef modeRef  = (__bridge CFStringRef)mode;
        CFRunLoopRunInMode(modeRef, 0.1, false);
    }
}
CFRelease(allModes);

但该方案存在一些缺点,需要衡量:

  • 因为崩溃发生,将程序的控制权交给新的 RunLoop所以不会返回原来的 RunLoop函数调用堆栈将不会释放造成泄漏

  • 虽然可以保活,但是会增加业务异常风险,所以需要衡量

线程保活

为什么线程做完事情就会退出?

NSThread 的一个工作流程如下:

start() -> 创建 pthread -> main() -> [target performSelector:selector] -> exit

NSThread 需要保活。为什么会死掉?看看 gnu 源码

- (void) start
{
  pthread_attr_t	attr;

  if (_active == YES)
    {
      [NSException raise: NSInternalInconsistencyException
                  format: @"[%@-%@] called on active thread",
        NSStringFromClass([self class]),
        NSStringFromSelector(_cmd)];
    }
  if (_cancelled == YES)
    {
      [NSException raise: NSInternalInconsistencyException
                  format: @"[%@-%@] called on cancelled thread",
        NSStringFromClass([self class]),
        NSStringFromSelector(_cmd)];
    }
  if (_finished == YES)
    {
      [NSException raise: NSInternalInconsistencyException
                  format: @"[%@-%@] called on finished thread",
        NSStringFromClass([self class]),
        NSStringFromSelector(_cmd)];
    }

  /* Make sure the notification is posted BEFORE the new thread starts.
   */
  gnustep_base_thread_callback();

  /* The thread must persist until it finishes executing.
   */
  RETAIN(self);

  /* Mark the thread as active while it's running.
   */
  _active = YES;

  errno = 0;
  pthread_attr_init(&attr);
  /* Create this thread detached, because we never use the return state from
   * threads.
   */
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  /* Set the stack size when the thread is created.  Unlike the old setrlimit
   * code, this actually works.
   */
  if (_stackSize > 0)
    {
      pthread_attr_setstacksize(&attr, _stackSize);
    }
  // 设置回调函数
  if (pthread_create(&pthreadID, &attr, nsthreadLauncher, self))
    {
      DESTROY(self);
      [NSException raise: NSInternalInconsistencyException
                  format: @"Unable to detach thread (last error %@)",
                  [NSError _last]];
    }
}

看看 pthread 创建后的回调函数

static void *
nsthreadLauncher(void *thread)
{
  NSThread *t = (NSThread*)thread;

  setThreadForCurrentThread(t);

  /*
   * Let observers know a new thread is starting.
   */
  if (nc == nil)
    {
      nc = RETAIN([NSNotificationCenter defaultCenter]);
    }
  // 发送通知
  [nc postNotificationName: NSThreadDidStartNotification
		    object: t
		  userInfo: nil];
	// 设置线程名
  [t _setName: [t name]];
	// 调用 main 方法
  [t main];
	// 线程退出
  [NSThread exit];
  // Not reached
  return NULL;
}

看了源码,会发现 NSThread 调用 start 内部就会调用 [NSThread exit] 所以会退出。要想常驻,就需要在 main 方法做 runloop 保活。

- (void) main
{
  if (_active == NO)
    {
      [NSException raise: NSInternalInconsistencyException
                  format: @"[%@-%@] called on inactive thread",
        NSStringFromClass([self class]),
        NSStringFromSelector(_cmd)];
    }

  [_target performSelector: _selector withObject: _arg];
}

main 方法内其实就是在执行 _selector。也就是在 NSThread 的初始化方法中,传入的 selector 中进行 runloop 保活逻辑。

应用场景:经常在子线程中处理某些逻辑的场景。如果销毁再创建再销毁再创建效率很低,这个情况下就需要线程保活。

@interface LifeThread : NSThread
@end
@implementation LifeThread
- (void)dealloc{
    NSLog(@"%s", __func__);
}
@end

@interface ViewController ()
@property (nonatomic, strong) LifeThread *thread;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.thread = [[LifeThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self performSelector:@selector(test) onThread:self.task withObject:nil waitUntilDone:NO];
}
- (void)test{
    NSLog(@"沿用保活的线程,处理任务:%@", [NSThread currentThread]);
}
// 该方法仅用于线程保活
- (void)run{
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
    // 给 RunLoop 的某个 Mode 里添加 Source/Timer/Observer。其中 addPort 就是 Source1
    [[NSRunLoop currentRunLoop] addPort:[[NSMachPort alloc] init] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"task finished");
}
@end

默认创建的 NSThread 会在 NSDefaultRunLoopMode 模式下运行,当 UI 滑动则进入 UITrackingMode 模式,所以 NSThread 的方法会停止。

线程保活就是给方法内部添加 RunLoop但是新创建的 RunLoop 运行肯定是基于某个 ModeRunLoop 持续运行(保活)的前提是必须有 Timer、Sources、Observer。所以在 Demo 中我们添加了 NSMachPort

上面的代码存在问题:

  1. ViewController 存在内存泄漏,initWithTarget:self 因为线程保活,所以 self 被持有,不会执行 dealloc

  2. LBPThread 线程不会死亡,假如我们需要在某个时机让保活线程销毁,现在是办不到的

  3. RunLoop 不会停止

改进:

  1. Thread 换种 api -(instancetype)initWithBlock:(void (^)(void))block,线程不持有 self

  2. [[NSRunLoop currentRunLoop] run] api 换掉。查看系统说明,底层其实就是一个无限循环,循环内部不断调用 runMode:beforeDate:。下面也有建议,建议我们想销毁 RunLoop可以替换 API比如设置一个变量标记是否需要结束 RunLoop

改进代码如下

__weak ViewController *weakself = self;
self.thread = [[LifeThread alloc] initWithBlock:^{
    NSLog(@"RunLoop Start");
    [[NSRunLoop currentRunLoop] addPort:[[NSMachPort alloc] init] forMode:NSDefaultRunLoopMode];
  	// tips2:
    while (!weakself.needStopThread) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
    NSLog(@"RunLoop Stop");
}];
[self.thread start];

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    if(!self.thread) return;
    [self performSelector:@selector(threadTask) onThread:self.thread withObject:nil waitUntilDone:NO];
}
#pragma mark - 线程相关
- (void)threadTask {
    NSLog(@"线程任务 %@", [NSThread currentThread]);
}

- (void)stopThread {
    if(!self.thread) return;
	  // tips1:
    [self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:YES];
}

- (void)stop {
    self.needStopThread = YES;
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.thread = nil;
}

- (void)dealloc {
    NSLog(@"%s", __func__);
    [self stopThread];
}

@end

但上面的代码还是存在问题:

  • 如果 stop 方法内部的 waitUntilDone 为 NO则可能会出现 Crash。因为该参数代表后续逻辑代表会不会等该 selector 执行完毕。因为为 NO所以 ViewController 执行 dealloc 了,所以 dealloc 方法和 Thread 内部的 block 同时进行,不能确保在 block 内部执行的时候 dealloc 有没有执行完,访问 weakSelf.isStoped 可能会出现坏内存访问,则会 crash

    解决方案:把 waitUntilDone 改为 YES

  • 但发现还存在问题:页面返回后,线程还是在执行打印任务。

    断点发现,在 NSThread 的 block 里while 条件中 weakself 已经为 nil 了。但 self.thread 还存在,且 block 里的逻辑while 条件取反后条件成立,则继续调用 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; RunLoop 持续运行

    解决方案:不能在 block 里添加 __strong typeof(weakself) strongSelf = weakself; 否则造成循环引用(VC -> thread -> block -> self(VC)页面消失thread 无法释放。

    正确的做法是在 while 条件中增加 weakself 是否为 nil 的判断。

    可能有些人会产生疑问:为什么 waitUntilDone 改为 YESdealloc 也就没执行完毕,为什么 weak 指针指向的 weakself 就为 nil 了?

    weak 指针的特性是:当对象开始销毁时(即 dealloc 被调用时),所有指向它的 weak 指针会立即被置为 nil。这一行为发生在 dealloc 方法执行之前,而不是之后。

继续优化版本:

__weak ViewController *weakself = self;
self.thread = [[LifeThread alloc] initWithBlock:^{
    NSLog(@"RunLoop Start");
    [[NSRunLoop currentRunLoop] addPort:[[NSMachPort alloc] init] forMode:NSDefaultRunLoopMode];
    while (weakself && !weakself.needStopThread) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
    NSLog(@"RunLoop Stop");
}];
[self.thread start];

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    if(!self.thread) return;
    [self performSelector:@selector(threadTask) onThread:self.thread withObject:nil waitUntilDone:YES];
}
#pragma mark - 线程相关
- (void)threadTask {
    NSLog(@"线程任务 %@", [NSThread currentThread]);
}

- (void)stopThread {
    if(!self.thread) return;
    [self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:YES];
}

- (void)stop {
    self.needStopThread = YES;
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.thread = nil;
}

- (void)dealloc {
    NSLog(@"%s", __func__);
    [self stopThread];
}

@end

效果如下:

注意: 

  • 线程的 RunLoop 结束了,线程也无法执行任务了,所以需要给线程对象设置为 nil。同时任务派发的地方也需要判断线程是否存在否则会 crash

  • NSRunLoop 常驻线程可以运行在 NSRunLoopCommonModes 下吗?

    不可以。因为在跑的时候如果 modeName 等于 kCFRunLoopCommonModes 则直接 kCFRunLoopRunFinished则 RunLoop 的 while 循环条件失败

    SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
        CHECK_FOR_FORK();
        if (modeName == NULL || modeName == kCFRunLoopCommonModes || CFEqual(modeName, kCFRunLoopCommonModes)) {
            static dispatch_once_t onceToken;
            dispatch_once(&onceToken, ^{
                CFLog(kCFLogLevelError, CFSTR("invalid mode '%@' provided to CFRunLoopRunSpecific - break on _CFRunLoopError_RunCalledWithInvalidMode to debug. This message will only appear once per execution."), modeName);
                _CFRunLoopError_RunCalledWithInvalidMode();
            });
            return kCFRunLoopRunFinished;
        }
        // ...
        return result;
    }
    

线程保活的目的是保证线程处于激活状态,而不是使用强指针让线程不要释放。为让其处于激活状态就需要使用 RunLoop。

线程封装

思考:如何设计一个常驻线程工具类?

继承自 NSThread 吗?不行,这样的话, api 不够收口,留的口子太多,不方便管控。

#import <Foundation/Foundation.h>

typedef void (^LBPPermenantThreadTask)(void);

@interface LBPPermenantThread : NSObject

/**
 开启线程
 */
- (void)run;

/**
 在当前子线程执行一个任务
 */
- (void)executeTask:(LBPPermenantThreadTask)task;

/**
 结束线程
 */
- (void)stop;

@end


#import "LBPPermenantThread.h"

/** MJThread **/
@interface LBPThread : NSThread
@end
@implementation LBPThread
- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end

/** MJPermenantThread **/
@interface LBPPermenantThread()
@property (strong, nonatomic) LBPThread *innerThread;
@property (assign, nonatomic, getter=isStopped) BOOL stopped;
@end

@implementation LBPPermenantThread
#pragma mark - public methods
- (instancetype)init
{
    if (self = [super init]) {
        self.stopped = NO;

        __weak typeof(self) weakSelf = self;

        self.innerThread = [[LBPThread alloc] initWithBlock:^{
            [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];

            while (weakSelf && !weakSelf.isStopped) {
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            }
        }];

        [self.innerThread start];
    }
    return self;
}

- (void)run
{
    if (!self.innerThread) return;
    [self.innerThread start];
}

- (void)executeTask:(LBPPermenantThreadTask)task
{
    if (!self.innerThread || !task) return;

    [self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}

- (void)stop
{
    if (!self.innerThread) return;

    [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    [self stop];
}

#pragma mark - private methods
- (void)__stop
{
    self.stopped = YES;
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.innerThread = nil;
}

- (void)__executeTask:(LBPPermenantThreadTask)task
{
    task();
}
@end

卡顿监控

RunLoop 监控卡顿,可以查看 带你打造一套 APM 系统 文章

AsyncDisplayKit

卡顿主要原因是 CPU/GPU 高负荷工作mask/cornerRadius/drawrect/opaque 带来的 offscreen rendering/blending 等),或者任务在时间分配下不均衡。

Autolayout 布局性能瓶颈。约束的计算会随着 View 数量和层级的增长呈指数级增长,且必须在主线程执行。

并行效率低。大多数情况下,主线程繁忙,其他子线程空余。所以思路是把主线程的任务转移一部分给其他线程进行异步处理,主线程带来性能提升

AsyncDisplayKit 主要针对:

  • 渲染对于大量文字、图片混合在一起时而文字区域的大小和布局恰恰依赖着渲染结果。ASDK 尽可能走后台线程进行渲染,完成后再同步回到主线程相应的 UIView
  • 布局。ASDK 抛弃了 Autolayout实现了自己的布局和缓存
  • 系统对象的创建和销毁。UIKit 封装了 CALayer 以支持出没灯显示以外的操作。耗时也增加了这些操作也需要在主线程进行。ASDK 基于 Node 的设计,突破了 UIKit 线程的限制。

ASDK 创建了 ASDisplayNode 对象,内部封装了 UIView/CALayer具有和 UIView/CALayer 相似的属性,例如 frame、backgroundColor这些属性都可以在子线程更改这样可以实现将排版和绘制放到后台线程但最终都需要将 View 的更改同步到主线程的 UIView/CALayer 中去。

这个同步时机,就是利用 RunLoop 实现的。系统的 UIKit/QuartzCore 也是 RunLoop 的业务方,同样,我们可以模仿系统行为,将针对 View 的改动,在主线程 RunLoop 添加一个 Observer监听 kCFRunLoopBeforeWaitingkCFRunLoopExit 状态,当收到回调时,遍历所有之前加入到队列中待处理的事务,然后一一执行。

+ (void)registerTransactionGroupAsMainRunloopObserver:(_ASAsyncTransactionGroup *)transactionGroup
{
  ASDisplayNodeAssertMainThread();
  static CFRunLoopObserverRef observer;
  ASDisplayNodeAssert(observer == NULL, @"A _ASAsyncTransactionGroup should not be registered on the main runloop twice");
  // defer the commit of the transaction so we can add more during the current runloop iteration
  CFRunLoopRef runLoop = CFRunLoopGetCurrent();
  CFOptionFlags activities = (kCFRunLoopBeforeWaiting | // before the run loop starts sleeping
                              kCFRunLoopExit);          // before exiting a runloop run
  CFRunLoopObserverContext context = {
    0,           // version
    (__bridge void *)transactionGroup,  // info
    &CFRetain,   // retain
    &CFRelease,  // release
    NULL         // copyDescription
  };

  observer = CFRunLoopObserverCreate(NULL,        // allocator
                                     activities,  // activities
                                     YES,         // repeats
                                     INT_MAX,     // order after CA transaction commits
                                     &_transactionGroupRunLoopObserverCallback,  // callback
                                     &context);   // context
  CFRunLoopAddObserver(runLoop, observer, kCFRunLoopCommonModes);
  CFRelease(observer);
}

static void _transactionGroupRunLoopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
  ASDisplayNodeCAssertMainThread();
  _ASAsyncTransactionGroup *group = (__bridge _ASAsyncTransactionGroup *)info;
  [group commit];
}

- (void)commit
{
  ASDisplayNodeAssertMainThread();

  if ([_containers count]) {
    NSHashTable *containersToCommit = _containers;
    _containers = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality];

    for (id<ASAsyncTransactionContainer> container in containersToCommit) {
      // Note that the act of committing a transaction may open a new transaction,
      // so we must nil out the transaction we're committing first.
      _ASAsyncTransaction *transaction = container.asyncdisplaykit_currentAsyncTransaction;
      container.asyncdisplaykit_currentAsyncTransaction = nil;
      [transaction commit];
    }
  }
}