iOS Runloop详解

913 阅读10分钟

目录

1.Runloop简介和作用

2.RunLoop的开启

3.RunLoop对象及获取

4.RunLoop的底层结构详解

5.RunLoop与线程之间的关系

6.RunLoop整体处理逻辑

7.RunLoop的应用


苹果已经开源了CoreFoundation源代码,CFRunloop源代码,后面我们将基于源码对runloop进行探究。 官方文档

1.Runloop简介和作用

  • 简介: Runloop就是一个“死”循环,在程序启动时,Runloop会不断的响应处理用户操作(如点击屏幕,划动屏幕),定时器,系统事件等,没有当所有事件都做完了就会进入休息等待状态,一旦有事件发生就会被唤醒去处理事件,保证程序的正常运作。

  • 基本作用: 1.保持程序持续运行,程序一启动就会开一个主线程,主线程一开起来就会跑一个主线程对应的RunLoop,RunLoop保证主线程不会被销毁,也就保证了程序的持续运行 2.处理APP的各种事件(比如:触摸事件,滑动事件,定时器事件,block等等) 3.节省CPU资源,提高程序性能,程序运行起来时,当什么操作都没有做的时候,RunLoop就通知CPU,现在没有事情做,进入休眠状态,这时CPU就会将其资源释放出来去做其他的事情,当有事情做的时候RunLoop就会立马起来去做事情。
    我们先通过苹果官方文档中的一张图片来简单看一下: 截屏2021-05-28 下午12.16.04.png

2.RunLoop的开启

  • UIApplicationMain函数内启动了Runloop,程序不会马上退出,而是保持运行状态。因此每一个应用必须要有一个runloop,我们知道主线程一开起来,就会跑一个和主线程对应的RunLoop,那么RunLoop一定是在程序的入口main函数中开启。
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

//进入UIApplicationMain
UIKIT_EXTERN int UIApplicationMain(int argc, char *argv[], NSString * __nullable principalClassName, NSString * __nullable delegateClassName);

//我们发现它返回的是一个int数,那么我们对main函数做一些修改
int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSLog(@"开始");
        int re = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        NSLog(@"结束");
        return re;
    }
}
/*
结论:运行程序,我们发现只会打印开始,并不会打印结束,这说明在UIApplicationMain函数中,
开启了一个和主线程相关的RunLoop,导致UIApplicationMain不会返回,一直在运行中,也就保证了程序的持续运行。
*/

我们看看Runloop的源码

/**
 开启RunLopp入口
 RunLoop就是一个do while死循环
 */
void CFRunLoopRun(void) {	/* DOES CALLOUT */
    int32_t result;
    do {
        //参数:当前线程循环,执行时候默认第一个mode为DefaultMode
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); // // 核心
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

我们发现RunLoop确实是do while循环通过判断result的值实现的。因此,我们可以把RunLoop看成一个死循环。如果没有RunLoop,UIApplicationMain函数执行完毕之后将直接返回,那么程序将直接退出了。

3.RunLoop对象及获取

  • Fundation框架 (基于CFRunLoopRef的封装)NSRunLoop对象

  • CoreFoundation CFRunLoopRef对象

因为Fundation框架是基于CFRunLoopRef的一层OC封装,这里我们主要研究CFRunLoopRef源码

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

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

4.RunLoop的底层结构详解

我们看看Runloop(CFRunLoopRef)结构体的源码:

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;			/* locked for accessing mode list */
    __CFPort _wakeUpPort;			// used for CFRunLoopWakeUp 
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

Runloop结构体里面有很多参数信息,我们主要看CFRunLoopModeRef 和 CFMutableSetRef类型的变量

CFRunLoopModeRef _currentMode; // 当前的mode
CFMutableSetRef _modes; //RunLoopMode的集合 包含很多mode

CFRunLoopModeRef 其实是指向__CFRunLoopMode结构体的指针,__CFRunLoopMode结构体源码如下:

typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;	/* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    
    CFMutableSetRef _sources0; 
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    
    CFMutableDictionaryRef _portToV1SourceMap;
    __CFPortSet _portSet;
    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 */
};

主要是以下四个成员变量:

CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;

通过上面的分析,我们对RunLoop内部结构有了大致的了解,接下来来详细分析RunLoop的相关类。以下为Core Foundation中关于RunLoop的5个类,包含关系如下:

CFRunLoopRef - 获得当前RunLoop和主RunLoop

CFRunLoopModeRef - RunLoop 运行模式,只能选择一种,在不同模式中做不同的操作

CFRunLoopSourceRef - 事件源,输入源
CFRunLoopTimerRef - 定时器时间
CFRunLoopObserverRef - 观察者 关系图如下:

截屏2021-06-01 下午5.39.28.png

下面我们具体分析一下 CFRunLoopModeRef(model),CFRunLoopSourceRef(source0,source1),CFRunLoopTimerRef(timer),Observer(CFRunLoopObserverRef)这些元素的作用。

4.1 CFRunLoopModeRef:

CFRunLoopModeRef代表Runloop的运行模式,通过源码和上图可以知道:

  • 在一个Runloop中包含了若干个Mode,每个Mode又每个Mode又包含若干个Source、Timer、Observer
  • 每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
  • 如果需要切换Mode,只能退出RunLoop,再重新指定一个Mode进入,这样做主要是为了分隔开不同组的Source、Timer、Observer,让其互不影响。如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出。

Mode的类型: 系统默认注册的5个Mode,比较常见的是1和5

  1. NSDefaultRunLoopMode:App的默认Mode,通常主线程是在这个Mode下运行
  2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
  3. NSConnectionReplyMode : 系统使用来监视应答,开发用不到。
  4. NSModalPanelRunLoopMode: 系统使用这种模式来标识用于模态面板的事件,开发用不到。
  5. NSRunLoopCommonMode: 这是一个占位用的Mode,作为标记NSDefaultRunLoopMode和UITrackingRunLoopMode用,并不是一种真正的Mode

Mode间的切换,看下段源码:

/** 
 开启RunLoop入口
 RunLoop就是一个do while死循环
 */
void CFRunLoopRun(void) {	/* DOES CALLOUT */
    int32_t result;
    do {
        //参数:当前线程循环,执行时候默认第一个mode为DefaultMode /
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); // // 核心
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}


//mode 怎么切换的
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    //状态的判断 如果runloop释放了 就直接返回finish
    if (__CFRunLoopIsDeallocating(rl))
        return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    
    //通过modeName查找mode 并赋值
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
        Boolean did = false; //Empty mode没有任何资源()
        if (currentMode) __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopUnlock(rl);
        
        //判断did 如果Empty mode没有任何资源则也返回finish
        return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
    
    // 1 保存 previousMode
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    /*
     当前的mode 为defalutMode  rl->_currentMode =  defalutMode
     当滑动的时候 modeName= trackingMode 即currentMode= trackingMode
     previousMode = defalutMode
     rl->_currentMode =  trackingMode
     */
    //previousMode 保存当前传进来的mode
    CFRunLoopModeRef previousMode = rl->_currentMode;//
    
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;

    //==========处理mode相关的东西=========//
	if (currentMode->_observerMask & kCFRunLoopEntry )
        //告诉系统Runloop的状态是进入状态kCFRunLoopEntry
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
     // 核心方法 处理mode里面的东西
	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

	if (currentMode->_observerMask & kCFRunLoopExit )
        //告诉系统Runloop的状态是kCFRunLoopExit 表示处理完了
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    //==========================//
    
    // 5 切换到之前model
    __CFRunLoopModeUnlock(currentMode);
    __CFRunLoopPopPerRunData(rl, previousPerRun);
    // rl->_currentMode = defalutMode  还原之前的mode
	rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}

总结如下:

1.开启runloop,执行时候默认第一个mode为defaultMode,所以currentMode为defaultMode
2.当我们滑动时,会用previousMode保存上一个mode,所以previousMode为defaultMode,而currentMode会被切换为trackingMode
3.当我们停止滑动时,则又会切换回原来的Mode,即currentMode = previousMode,所以currentMode又变为了defaultMode 通过上面源码mode的切换分析我们就可以理解,为什么添加一个NSTimer(默认在defaultMode里面),在滑动的时候(切换为tackingMode)timer就停止了,停止滑动timer又正常运行了(又还原回defaultMode了)。

下面通过代码来看看平时使用NSTimer问题:

- (void)viewDidLoad {
   [super viewDidLoad];
   
   NSTimer * timer  = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
  //把定时器添加到RunLoop中,并且选择默认运行模式NSDefaultRunLoopMode = kCFRunLoopDefaultMode
   [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
   
  // 当我们将timer添加到UITrackingRunLoopMode模式中,此时只有我们在滑动textField时timer才会运行
   // [[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
   
   UIScrollView *scrollView = [[UIScrollView alloc] init];
   scrollView.backgroundColor = [UIColor redColor];
   scrollView.frame = CGRectMake(10, 100, 300, 300);
   scrollView.contentSize = CGSizeMake(300, 800);
   scrollView.delegate = self;
   [self.view addSubview:scrollView];

}

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
   NSLog(@"scrollViewDidEndDecelerating");
}


-(void)timerAction{
   
   NSLog(@"timerAction");
}

通过打印发现,当我们一直拖动scrollview时,timer停止运行了,然后停止拖动scrollview,timer又开始运行了,由此印证了上面的结论。

截屏2021-06-02 上午10.50.43.png

如何解决:

    /*
    使用占位的运行模式 NSRunLoopCommonModes
    被NSRunLoopCommonModes标记的都可以运行,因此我们只需把Mode设置为NSRunLoopCommonModes,
    timer就可以在UITrackingRunLoopMode,kCFRunLoopDefaultMode两种模式下运行
    */
   [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

4.2 Source0 与 Source1

  • Source1:基于mach_Port的,来自系统内核或者其他进程或线程的事件,可以主动唤醒休眠中的RunLoop(iOS里进程间通信开发过程中一般由系统控制)。mach_port大家就理解成进程间相互发送消息的一种机制就好。

  • Source0: 非基于Port的 处理事件,用于用户的主动触发,如按钮点击,屏幕点击事件,PerformSelectors 下面我们写一个点击屏幕方法和PerformSelectors事件看看里面的信息: 截屏2021-06-01 下午2.58.52.png

截屏2021-06-01 下午3.02.20.png

如上图通过断点打印可以看出,触摸事件和PerformSelectors是会触发Source0事件。

  • 事件触发整体流程:

当我们触发了事件(触摸/锁屏/摇晃等)后,由IOKit.framework生成一个 IOHIDEvent事件,而IOKit是苹果的硬件驱动框架,由它进行底层接口的抽象封装与系统进行交互传递硬件感应的事件,并专门处理用户交互设备,由IOHIDServices和IOHIDDisplays两部分组成,其中IOHIDServices是专门处理用户交互的,它会将事件封装成IOHIDEvents对象,接着用mach port转发给需要的App进程,随后 Source1就会接收IOHIDEvent,之后再回调__IOHIDEventSystemClientQueueCallback(),__IOHIDEventSystemClientQueueCallback()内触发Source0Source0 再触发 _UIApplicationHandleEventQueue()。所以触摸事件看到是在 Source0 内的。

4.3 Timers : 定时器,NSTimer

截屏2021-06-01 下午3.11.32.png 通过打印可以很明显看到,定时器就是一个RunloopDoTimer事件

4.4 Observer:

Observer : 监听器,用于监听RunLoop的状态,通过不同的状态,我们可以根据业务需求做相关处理。 直接看代码:

/*
  创建监听者
 CFRunLoopObserverCreate(<#CFAllocatorRef allocator#>, <#CFOptionFlags activities#>, <#Boolean repeats#>, <#CFIndex order#>, <#CFRunLoopObserverCallBack callout#>, <#CFRunLoopObserverContext *context#>)
 
第一个参数 CFAllocatorRef allocator:分配存储空间 CFAllocatorGetDefault()默认分配
第二个参数 CFOptionFlags activities:要监听的状态 kCFRunLoopAllActivities 监听所有状态
第三个参数 Boolean repeats:YES:持续监听 NO:不持续
第四个参数 CFIndex order:优先级,一般填0即可
第五个参数 :回调 两个参数observer:监听者 activity:监听的事件
*/

/*
Observer的所有状态
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
};
*/

CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
      switch (activity) {
          case kCFRunLoopEntry:
              NSLog(@"kCFRunLoopEntry:RunLoop进入");
              break;
          case kCFRunLoopBeforeTimers:
              NSLog(@"kCFRunLoopBeforeTimers:RunLoop即将处理Timers了");
              break;
          case kCFRunLoopBeforeSources:
              NSLog(@"kCFRunLoopBeforeSources:RunLoop即将处理Sources了");
              break;
          case kCFRunLoopBeforeWaiting:
              NSLog(@"kCFRunLoopBeforeWaiting:RunLoop即将休眠了");
              break;
          case kCFRunLoopAfterWaiting:
              NSLog(@"kCFRunLoopAfterWaiting:RunLoop醒来了");
              break;
          case kCFRunLoopExit:
              NSLog(@"kCFRunLoopExit:RunLoop退出了");
              break;

          default:
              break;
      }
  });
  
// 给RunLoop添加监听者
/*
第一个参数 CFRunLoopRef rl:要监听哪个RunLoop,这里监听的是主线程的RunLoop
第二个参数 CFRunLoopObserverRef observer 监听者
第三个参数 CFStringRef mode 要监听RunLoop在哪种运行模式下的状态
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

//如果是子线程 需要开启runloop
[[NSRunLoop currentRunLoop] run];


/*
CF的内存管理(Core Foundation)
凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release
*/
CFRelease(observer);

5.RunLoop与线程之间的关系

我们先来看看RunLoop与线程之间主要几个点:

  • 每条线程都有唯一的一个与之对应的RunLoop对象
  • RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
  • 主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建
  • RunLoop在第一次获取时创建,在线程结束时销毁

通过源码来验证一下:

/** 
 开启RunLoop入口
 RunLoop就是一个do while死循环
 */
void CFRunLoopRun(void) {	/* DOES CALLOUT */
    int32_t result;
    do {
        //runloop启动时 就需要调用CFRunLoopGetCurrent()
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); 
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

// 拿到当前Runloop 调用_CFRunLoopGet0
CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

//传入线程 返回线程对应的runloop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
        // t==0表示为主线程
        t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
        //这里很明显可以看出创建了一个一个字典
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        // 根据传入的主线程获取主线程对应的RunLoop
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        // 保存主线程 将主线程-key和RunLoop-Value保存到字典中
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
	if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
	    CFRelease(dict);
	}
	CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    //通过一个字典__CFRunLoops 来获取runloop, key:线程
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    
    // 如果loop为空,则创建一个新的loop,这里也是解释了为什么runloop会在第一次获取的时候创建
    if (!loop) {
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
         
            // 创建好之后,以线程为key runloop为value,一对一存储在字典中,下次获取的时候,则直接返回字典内的runloop
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
        CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

整理得出:
1.在线程中调用开启RunLoop方法时会去通过当前线程获取对应的runloop(主线程默认就会调用)
2.如果当前线程没有runloop,则会自动去创建一个runloop
3.以线程为key,runloop为value,一对一存储在字典中,下次获取直接在字典里面取,这也充分的表明了为什么线程和runloop是一一对应的关系。

6.RunLoop整体处理逻辑

先分析一下源码:

// 精简上面写的 CFRunLoopRunSpecific 函数代码,其内部会调用__CFRunLoopRun函数
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */

    // 通知Observers : 进入Loop
    // __CFRunLoopDoObservers内部会调用 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
函数
    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);

    return result;
}

// 精简后的 __CFRunLoopRun函数,保留了主要代码
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    int32_t retVal = 0;
    do {
        // 通知Observers:即将处理Timers
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); 
        
        // 通知Observers:即将处理Sources
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        
        // 处理Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 处理Sources0
        if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
            // 处理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        // 如果有Sources1,就跳转到handle_msg标记处
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            goto handle_msg;
        }
        
        // 通知Observers:即将休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        
        // 进入休眠,等待其他消息唤醒
        __CFRunLoopSetSleeping(rl);
        __CFPortSetInsert(dispatchPort, waitSet);
        do {
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        } while (1);
        
        // 醒来
        __CFPortSetRemove(dispatchPort, waitSet);
        __CFRunLoopUnsetSleeping(rl);
        
        // 通知Observers:已经唤醒
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
    handle_msg: // 看看是谁唤醒了RunLoop,进行相应的处理
        if (被Timer唤醒的) {
            // 处理Timer
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
        }
        else if (被GCD唤醒的) {
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else { // 被Sources1唤醒的
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
        }
        
        // 执行Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 根据之前的执行结果,来决定怎么做,为retVal赋相应的值
        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;
        }    
    } while (0 == retVal);
    
    return retVal;
}

同样为了方便理解,我们根据源码用图表示一下:

截屏2021-06-02 下午8.26.16.png

7.RunLoop的应用

1.线程保活: 可参考下一篇文章iOS 常驻线程

2.自动释放池的应用:
第一个observer监听事件kCFRunLoopEntry,回调调用_objc_autoreleasePoolPush()创建自动释放池,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个observer监听两个事件:

  • Before waiting时调用_objc_autoreleasePoolPop和push清空自动释放池。
  • Exit时调用_objc_autoreleasePoolPop释放清空。 之前的文章也有详细解析:autoreleasePool原理总结

3.监控卡顿。

4.性能优化:大量数据展示(如直播间弹幕)刷新方案。


文章中若有纰漏不足之处,欢迎指出,谢谢~