美文网首页
iOS-浅谈OC中Runloop的运行逻辑和应用

iOS-浅谈OC中Runloop的运行逻辑和应用

作者: 晴天ccc | 来源:发表于2019-06-19 08:57 被阅读0次

    目录

    • RunLoop的运行逻辑
    • RunLoop的应用

    RunLoop的运行逻辑

    运行逻辑流程图:

    我们要有一个概念, Runloop说到底就是一个do - while循环。
    因为自己能力有限,借鉴了一个大佬的一份对Runloop内部代码的整理片段:

    /// 用DefaultMode启动
    void CFRunLoopRun(void) {
        CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
    }
     
    /// 用指定的Mode启动,允许设置RunLoop超时时间
    int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
        return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
    }
     
    /// 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 对应的线程会一直停留着这个循环里,直到超时或者被手动停止,这个函数才会返回。

    上面的代码如果不好理解,下面有精简版的:(当 RunLoop 进行回调时,一般都是通过一个很长的函数调用出去 (call out), 当你在你的代码中下断点调试时,通常能在调用栈上看到这些函数。下面是这几个函数的整理版本)

    
    {
        /// 1. 通知Observers,即将进入RunLoop
        /// 此处有Observer会创建AutoreleasePool: _objc_autoreleasePoolPush();
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
        do {
     
            /// 2. 通知 Observers: 即将触发 Timer 回调。
            __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
            /// 3. 通知 Observers: 即将触发 Source (非基于port的,Source0) 回调。
            __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
            __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
     
            /// 4. 触发 Source0 (非基于port的) 回调。
            __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
            __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
     
            /// 6. 通知Observers,即将进入休眠
            /// 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
            __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
     
            /// 7. sleep to wait msg.
            mach_msg() -> mach_msg_trap();
            
     
            /// 8. 通知Observers,线程被唤醒
            __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
     
            /// 9. 如果是被Timer唤醒的,回调Timer
            __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
     
            /// 9. 如果是被dispatch唤醒的,执行所有调用 dispatch_async 等方法放入main queue 的 block
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
     
            /// 9. 如果如果Runloop是被 Source1 (基于port的) 的事件唤醒了,处理这个事件
            __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
     
     
        } while (...);
     
        /// 10. 通知Observers,即将退出RunLoop
        /// 此处有Observer释放AutoreleasePool: _objc_autoreleasePoolPop();
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
    }
    
    

    如果上面的代码还不好理解,下面还有更精简版的:

    SetUpThisRunLoopRunTimeoutTimer();
        do {
            //通知 Observers: 即将触发 Timer 回调。
            __CFRunLoopDoObservers(kCFRunLoopBeforeTimers);
            // 通知 Observers: 即将触发 Source (非基于port的,Source0) 回调。
            __CFRunLoopDoObservers(kCFRunLoopBeforeSources);
            
            __CFRunLoopDoBlocks();
            //触发 Source0 (非基于port的) 回调。
            __CFRunLoopDoSource0();
            
            CheckIfExistMessagesInMainDispatchQueue();//看GCD有没有给过来需要去处理的任务
            //通知Observers,即将进入休眠
            __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);
            
            //通过 mach_msg_trap 来捕获通过mach_msg传来的消息,类似于睡着的时候撑起一直耳朵
            var wakeUpPort = sleepAndWaitForWakingUpPorts();
            // mach_msg_trap
            // Zzz...
            // 获取到 mach_msg 传来的消息,通知“被唤醒了”
            __CFRunLoopDoObservers(kCFRunLoopAfterWaiting);
            //处理
            if (wakeUpPort == timePort) {
            //如果是被Timer唤醒的,回调Timer
                __CFRunLoopDoTimer();
            } else if (wakeUpPort == MainDispatchQueuePort) {
                //GCD 如果是被dispatch唤醒的,执行所有调用 dispatch_async 等方法放入main queue 的 block
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE_();
            } else {
            //如果如果Runloop是被 Source1 (基于port的) 的事件唤醒了,处理这个事件
                __CFRunLoopDoSource1()
            }
            __CFRunLoopDoBlocks();
        } while (! stop && ! timeOut);
    
    //通知Observers,即将退出RunLoop
    __CFRunLoopDoObservers(kCFRunLoopExit);
    
    

    以上均为大佬所整理,借鉴过来便于自己学习和理解。

    iOS项目中Runloop实现流程

    我们知道在main函数启动时,会有Runloop的用DefaultMode默认启动和使用指定Mode进行启动。

    main函数

    UIApplicationMain函数

    启动主线程Runloop

    CFRunloopRun()

    RunLoop的应用

    • 事件响应

    苹果注册了一个Source1来接受系统事件,回调函数为:__IOHIDEventSystemClientQueueCallback()

    当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由SpringBoard 接收。
    SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给需要的App进程。
    随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。

    _UIApplicationHandleEventQueue()会把IOHIDEvent处理并包装成UIEvent进行处理或分发。
    其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow等。
    通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。

    • AutoreleasePool

    相关文章

      网友评论

          本文标题:iOS-浅谈OC中Runloop的运行逻辑和应用

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