美文网首页
iOS-RunLoop

iOS-RunLoop

作者: doudo | 来源:发表于2017-09-20 10:46 被阅读28次

    本文主要内容:

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

    一、概念

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

    事件循环

    二、数据结构

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

    1.CFRunLoop

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

    2.CFRunLoopMode

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


      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也是一对多的关系:



    同一时间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 会直接退出,不进入循环。

    三、事件循环的实现机制

    四、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

          本文链接:https://www.haomeiwen.com/subject/sovnsxtx.html