iOS-RunLoop

本文主要内容:

  • 概念
  • 数据结构
  • 事件循环的实现机制
  • RunLoop与NSTimer
  • RunLoop与线程
  • 源码

一、概念

RunLoop是通过内部维护的事件循环来对事件和消息进行管理的一个对象。

iOS-RunLoop_第1张图片
事件循环

二、数据结构

OSX/iOS 系统中,提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef。
CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。

1.CFRunLoop

iOS-RunLoop_第2张图片
CFRunLoop数据结构
  • pthread,runloop与线程是一一对应的关系。
  • currentMode,是一个CFRunLoopMode的一种数据,结构,表示当前runloop是那种model。
  • modes,NSMutableSet,是一个model集合。
  • commonModes,,NSMutableSet,是一个字符串的集合。这个mode会在下边CFRunLoopMode具体说明。
  • commonModeItes,也是个集合,集合中的内容是添加到当前runloop不同mode中的所有items:source、timer、observer。

2.CFRunLoopMode

iOS-RunLoop_第3张图片
CFRunLoopMode数据结构
  • name,实质是字符串,不同mode的名字。
  • sources0、sources1,都是MutableSet集合类型。区别,如下图:


    iOS-RunLoop_第4张图片
    sources0和sources1的区别
  • observers和timers,都是数组类型。

3.Source/Timer/Observer

  • CFRunLoopTimer,基于事件的定时器,和NSTimer是toll-free bridge的(免费桥转换)。
  • CFRunLoopObserver,我们可以通过注册一些observer来实现对runloop的相关时间点的监测。
    观测的时间点:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), //即将进入run loop
    kCFRunLoopBeforeTimers = (1UL << 1), //即将处理timer
    kCFRunLoopBeforeSources = (1UL << 2), //即将处理source
    kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6), //被唤醒但是还没开始处理事件
    kCFRunLoopExit = (1UL << 7), //run loop已经退出
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

4.RunLoop的mode

RunLoop与mode的关系是一对多的关系,mode与timer、source、observer也是一对多的关系:


iOS-RunLoop_第5张图片

iOS-RunLoop_第6张图片

同一时间RunLoop只能在一种mode下运行,如果需要切换mode,需要停止当前RunLoop,然后重启RunLoop。

model 主要是用来指定事件在运行循环中的优先级的,分为:

  • NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态
  • UITrackingRunLoopMode:ScrollView滑动时
  • UIInitializationRunLoopMode:启动时
  • NSRunLoopCommonModes(kCFRunLoopCommonModes):commonModes并不是一个实际存在的mode(CFRunLoopMode),而是字符串的集合,它是同步timer、observer、source到多个mode的一种技术方案。

苹果公开给我们的有两个:
NSDefaultRunLoopMode和NSRunLoopCommonModes。

一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。RunLoop 启动前内部必须要有至少一个 Timer/Observer/Source,如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

三、事件循环的实现机制

iOS-RunLoop_第7张图片

四、RunLoop与NSTimer

timer默认被添加到RunLoop的NSDefaultRunLoopMode中,tableview滑动时,RunLoop的mode会从kCFRunLoopDefaultMode切换到UITrackingRunLoopMode,此时定时器就失效了。
解决方案
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
实际是通过函数:
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName);

五、RunLoop与线程

  1. 线程和RunLoop是一一对应的。自己创建的线程默认是没有RunLoop的。
  2. 怎样开启一个常驻线程?
    1.为当前线程开启一个RunLoop,2.向该RunLoop中添加port、source来维持RunLoop的事件循环,3.启动RunLoop。
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];

线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。其实,其他线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。

六、源码加深理解

/// RunLoop的实现
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
    
    /// 首先根据modeName找到对应mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
    /// 如果mode里没有source/timer/observer, 直接返回。
    if (__CFRunLoopModeIsEmpty(currentMode)) return;
    
    /// 1. 通知 Observers: RunLoop 即将进入 loop。
    __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
    
    /// 内部函数,进入loop
    __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
        
        Boolean sourceHandledThisLoop = NO;
        int retVal = 0;
        do {
 
            /// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
            /// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
            /// 执行被加入的block
            __CFRunLoopDoBlocks(runloop, currentMode);
            
            /// 4. RunLoop 触发 Source0 (非port) 回调。
            sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
            /// 执行被加入的block
            __CFRunLoopDoBlocks(runloop, currentMode);
 
            /// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
            if (__Source0DidDispatchPortLastTime) {
                Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
                if (hasMsg) goto handle_msg;
            }
            
            /// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
            if (!sourceHandledThisLoop) {
                __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
            }
            
            /// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
            /// • 一个基于 port 的Source 的事件。
            /// • 一个 Timer 到时间了
            /// • RunLoop 自身的超时时间到了
            /// • 被其他什么调用者手动唤醒
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
                mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
            }
 
            /// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
            
            /// 收到消息,处理消息。
            handle_msg:
 
            /// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
            if (msg_is_timer) {
                __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
            } 
 
            /// 9.2 如果有dispatch到main_queue的block,执行block。
            else if (msg_is_dispatch) {
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            } 
 
            /// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
            else {
                CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
                sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
                if (sourceHandledThisLoop) {
                    mach_msg(reply, MACH_SEND_MSG, reply);
                }
            }
            
            /// 执行加入到Loop的block
            __CFRunLoopDoBlocks(runloop, currentMode);
            
 
            if (sourceHandledThisLoop && stopAfterHandle) {
                /// 进入loop时参数说处理完事件就返回。
                retVal = kCFRunLoopRunHandledSource;
            } else if (timeout) {
                /// 超出传入参数标记的超时时间了
                retVal = kCFRunLoopRunTimedOut;
            } else if (__CFRunLoopIsStopped(runloop)) {
                /// 被外部调用者强制停止了
                retVal = kCFRunLoopRunStopped;
            } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
                /// source/timer/observer一个都没有了
                retVal = kCFRunLoopRunFinished;
            }
            
            /// 如果没超时,mode里没空,loop也没被停止,那继续loop。
        } while (retVal == 0);
    }
    
    /// 10. 通知 Observers: RunLoop 即将退出。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}

资料:
深入理解RunLoop
深入理解 RunLoop
iOS线下分享《RunLoop》by 孙源@sunnyxx

你可能感兴趣的:(iOS-RunLoop)