美文网首页
RunLoop底层学习

RunLoop底层学习

作者: 朝夕向背 | 来源:发表于2018-11-22 19:02 被阅读0次

    概述

    int main(int argc, char * argv[]) {
      @autoreleasepool {
          return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
      }
    }
    

    这是iOS项目中都会有的main.h文件,它底层的伪代码大概如此:

    int main(int argc, char * argv[]) {
      @autoreleasepool {  
          int retVal = 0;
          do {
              //睡眠中等待消息
              int message = sleep_and_wait();
              //处理消息
              retVal = process_message(message);
          } while (0 == retVal);
          return 0;
      }
    }
    
    • RunLoop,运行循环,在程序运行过程中,能保证循环做一些事情。
    • runloop和线程的关系
      程序的运行都是在线程中进行的,每条线程都有唯一的一个与之对应的RunLoop对象。为了保证这种唯一性,RunLoop保存在一个全局的Dictionary里,线程作为key值,RunLoop作为value值。
        CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        __CFUnlock(&loopsLock);
        if (!loop) {
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
            __CFLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            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);
        }
    

    线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建;
    RunLoop会在线程结束时销毁;
    主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop。

    • 应用:RunLoop的应用范围很广,在定时器、GCD、performSelector、事件响应、手势识别、界面刷新、网络请求、AutoreleasePool等等很多地方用到。

    • 作用:
      1、保持程序的持续运行;
      2、处理App中的各种事件(比如触摸事件,定时器事件等等);
      3、节省CPU资源,提高程序的性能:该做事时做事,该休息时休息。

    RunLoop五个类和结构关系

    1、CFRunLoopRef

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

    2、CFRunLoopModeRef

    typedef struct __CFRunLoopMode *CFRunLoopModeRef;
    struct __CFRunLoopMode {
         ...
        CFStringRef _name;
         ...
        CFMutableSetRef _sources0;
        CFMutableSetRef _sources1;
        CFMutableArrayRef _observers;
        CFMutableArrayRef _timers;
        ...
    };
    

    3、CFRunLoopSourceRef;
    4、CFRunLoopObserverRef;
    5、CFRunLoopTimerRef
    从上面的图中可以看出,CFRunLoopRef中包含CFRunLoopModeRef,CFRunLoopModeRef包含_source_observer_timer

    • CFRunLoopModeRef代表RunLoop的运行模式;
    • 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer;
    • RunLoop启动时只能选择其中一个Mode,作为currentMode;
    • 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入。
      ✔️不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响。
    • 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出。

    常见的两种Mode

    • kCFRunLoopDefaultMode( NSDefaultRunLoopMode):APP的默认Mode,通常主线程是在这个Mode下运行

    • UITrackingRunLoopMode:界面追踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响

    • Source0
      触摸事件处理
      performSelector:onThread:
      点击处理

    • Source1
      基于Port的线程间通信
      系统事件捕捉

    • Timers
      NSTimer
      performSelector:withObject:afterDelay:

    • Observers
      用于监听RunLoop的状态
      UI刷新(BeforeWaiting)
      Autorelease pool(BeforeWaiting)

    RunLoop运行逻辑

    Int32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {  
         ......
             //通知Observers,进入Loop
         __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
            //具体要做的事
        result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
         //通知Observers,退出Loop
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
        ......
        return result;
    }
    
    static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
        .......
        do {
        ........
           //通知Observers:即将处理Timers
            if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
            //通知Observers:即将处理Sources
            if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
             //处理Block
            __CFRunLoopDoBlocks(rl, rlm);
            //处理Sources0
            Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
            if (sourceHandledThisLoop) {
                //处理Block
                __CFRunLoopDoBlocks(rl, rlm);
           }
            Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
              //判断有无Source1
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                //如果有Source1,就跳转到handle_msg
                goto handle_msg;
            }
            didDispatchPortLastTime = false;
           //通知Observers:即将休眠
           __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
            __CFRunLoopSetSleeping(rl);
    
            CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
            .......
            //等待别的消息来唤醒当前线程
           __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
           ......
          __CFRunLoopUnsetSleeping(rl);
            //通知Observers:结束休眠
          __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
    
            handle_msg:;
            ......
            //被Timers唤醒
           if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                CFRUNLOOP_WAKEUP_FOR_TIMER();
                __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())
            } else if (livePort == dispatchPort) {//被GCD唤醒
            .......
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            .......
            } else {
            //处理Source1
             __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
        }
         //处理Block
        __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);
        ......
        return retVal;
    }
    
    • 1、通知Observers监听器,即将进入Loop;
    • 2、通知Observers监听器,即将处理Timers
    • 3、通知Observers监听器,即将处理Sources
    • 4、处理被加入的block
    • 5、处理Source0事件。这个事件被处理后,会在一个循环结束后被使用;
    • 6、判断是否有Source1,如果有,则跳转到handle_msg标签处执行;
    • 7、通知Observers,开始休眠;
    • 8、通知Observers,结束休眠,也是handle_msg标签处
      ✔️处理Timer
      ✔️处理GCD Async To Main Queue
      ✔️处理Source1
    • 9、处理blocks
    • 10、根据前面的执行结果,决定如何操作
      ✔️是继续循环
      ✔️还是退出Loop
    • 11、通知Observers,退出Loop

    RunLoop的休眠原理

    RunLoop的休眠原理

    操作系统提供了两个API

    • 内核层面的API
    • 应用层面的API

    当API在应用界面(用户态)时,调用mach_msg(),就会转到内核态,然后调用内核态的mach_msg(),当没有消息时,就线程休眠,有消息就唤醒线程。

    RunLoop面试题

    1、讲讲Runloop,项目中有用到吗
    ✔️控制线程生命周期(线程保活)
    AFNetworking使用RunLoop技术控制子线程的生命周期,让子线程一直在内存中,当需要接口请求是,唤醒AFNetworking。不停的创建子线程销毁子线程会影响性能。
    ✔️解决NSTimer在滑动时停止工作的问题
    ✔️监控应用卡顿
    ✔️性能优化
    2、runloop内部实现逻辑
    参考RunLoop运行逻辑
    3、runloop和线程的关系
    每条线程都有唯一的一个与之对应的RunLoop对象
    RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
    线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
    RunLoop会在线程结束时销毁
    主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
    4、timer与runloop的关系
    RunLoop里面有个CFMutableSetRef _modes。这个模式里面有个CFMutableArrayRef _timers;
    5、程序中添加每3秒响应一次的NSTimer,当拖动tableview时timer可能无法响应要怎么解决?
    原因:定时器是在UITrackingRunLoopMode下工作,当拖动时,模式切换到kCFRunLoopDefaultMode模式下,定时器就停止工作。
    解决办法:创建定时器,添加NSDefaultRunLoopMode和UITrackingRunLoopMode模式
    6、runloop是怎么响应用户操作的,具体流程是什么样的
    Source1捕捉用户操作,然后把这个事件包装成事件队列EventQueue,然后放到source0中处理
    7、说说runloop的几种状态
    kCFRunLoopEntry:即将进入Loop
    kCFRunLoopBeforeTimers:即将处理Timers
    kCFRunLoopBeforeSources:即将处理Source
    kCFRunLoopBeforeWaiting:即将进入休眠
    kCFRunLoopAfterWaiting:刚从休眠中唤醒
    kCFRunLoopExit:即将退出Loop
    8、runloop的mode作用是什么
    常见的2中Mode
    kCFRunLoopDefaultMode( NSDefaultRunLoopMode):APP的默认Mode,通常主线程是在这个
    Mode下运行
    UITrackingRunLoopMode:界面追踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
    9、RunLoop自动释放池
    1、自动释放池第一次创建:当runloop启动的时候
    2、自动释放池最后一次销毁:当runloop退出的时候
    3、自动释放池其他时间的创建和销毁:当runloop即将进入都休眠的时候,会把之前的自动释放池释放,重新创建一个新的自动释放池。

    相关文章

      网友评论

          本文标题:RunLoop底层学习

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