美文网首页
runloop原理

runloop原理

作者: 晨阳Xia | 来源:发表于2021-03-17 18:20 被阅读0次

    打印函数调用栈

    bt 
    

    什么是runloop

    运行循环,在程序运行过程中循环做一些事情

    应用范畴

    • 定时器(timer)performselecter
    • GCD Async Main Queue
    • 网络请求,
    • AutoReleasePool

    UIApplicationMain(argc, argv, nil, appDelegateClassName); 内部实现了runloop

    @autoreleasepool {
    int retVal = 0;
    do {
    int message = sleep_and_wait();
    retVal = precess_message(message);

     }while (0 == retVal)
     return 0;
    

    }

    runloop的作用

    1. 保证程序的持续运行
    2. 处理app中的各种事件(比如触摸事件,定时器事件)
    3. 节省cpu资源,提高程序性能,该做事时做事该休息时休息

    线程与runloop对象的关系

    1. runloop跟线程的关系:每一条线程都有唯一一个与之对应的runloop对象
    2. runloop保存在一个全局的dictionary里,线程作为key,runloop作为value
    3. 线程刚创建时并没有runloop对象,Runloop会在第一次获取他时创建(主线程的runloop是在UIApplicationMain 获取runloop的时候创建的)
    4. runloop会在线程结束时销毁
    5. 主线程的runloop已经自动获取,子线程默认没有开启runloop

    runloop 懒加载 和 线程一对一的源码:

     if (!loop) {
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
            __CFLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
    

    与runloop相关的类

    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;
    };
    
    1. CFRunloopRef
    2. CFRunloopModeRef
    3. CFRunloopSourceRef
    4. CFRunloopTimerRef
    5. CFRunloopObserverRef

    CFRunloopModeRef

    • CFRunloopModeRef 代表Runloop的运行模式
    • 一个runloop包含若干个mode,每个mode又包含若干个Source0/Source1/Timers/Observers
    • runloop启动时只能选择一个mode作为currentMode
    • 如果需要切换mode,只能退出当前runloop(并不是代码执行完毕,而是在runloop内部切换了mode,上一个循环的内容结束而已,开始新的模式循环),再重新选择一个mode进入(好处:可以保证app专注于当前模式,不同组的source0/source1/timer/observe 能分隔开来,互不影响)
    • 如果mode没有任何的source0/source1/timer/observe,runloop会立马退出

    常见的两个模式

    kCFRunLoopDefaultMode:app的默认mode,通常主线程是在这个mode下运行
    UITrackingRunLoopMode:界面跟踪mode,用于scrollview朱总触摸滑动,保证界面滑动时不受其他mode的影响

    runloop一旦启动,处理的内容

    Source0:

    • 触摸事件
    • performSelector:onThread:
      Source1:
    • 基于port的线程通信
    • 系统事件的捕捉(点击事件一开始属于source1,然后包装,给source0来处理)
      Timer:
    • NSTimer
    • performSelector:withObject:afterDelay:
      Observers:
    • 用于监听Runloop的状态 (监听到runloop准备睡觉了,才开始刷新设置的背景颜色和自动释放池释放内存)
    • UI刷新

    检测runloop状态的代码

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunloopActivites, NULL);
        CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
        CFRelease(observer);
    };
    
    
     // Run Loop Observer Activities
     typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
         kCFRunLoopEntry = (1UL << 0),
         kCFRunLoopBeforeTimers = (1UL << 1),
         kCFRunLoopBeforeSources = (1UL << 2),
         kCFRunLoopBeforeWaiting = (1UL << 5),
         kCFRunLoopAfterWaiting = (1UL << 6),
         kCFRunLoopExit = (1UL << 7),
         kCFRunLoopAllActivities = 0x0FFFFFFFU
     };
    
    
    
    void observeRunloopActivites(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
        switch (activity) {
           
            default:
                break;
        }
    }
    
    或者
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            switch (activity) {
           
            default:
                break;
        }
        });
    CFRelease(observer);
    

    gcd和runloop的关系

    gcd不依赖于runloop(在子线程里做事情不依赖runloop)但是gcd回到主线程做事情后,会依赖runloop
    这也可以反映出:回到主线程刷新UI的原因

    runloop运行逻辑

    image.png

    runloop的休眠跟while循环是不一样的

    while的循环cpu一直在执行
    而runloop的休眠是mach_msg内核层面的api实现的


    image.png

    这种休眠原理真正的达到节省cpu资源的能力。

    timer和mode的关系

    image.png

    timer被添加到mode里。
    timer会唤醒runloop
    如果timer被设置为commonmode 则timer被放在 _commonModeItems里去了

    runloop

    睡觉之前 将颜色渲染出来,设置按钮的文字,点击事件的处理,清空释放池
    定时器可以唤醒runloop

    runLoop 源码

    CoreFoundation 中关于runloop的五个类
    CFRunloopRef
    CFRunloopModeRef
    CFRunloopSourceRef
    CFRunloopTimerRef
    CFRunloopObserverRef
    
    typedef struct __CFRunloop * CFRunloopRef;
    struct __CFRunloop {
        pthread_t _pthread;
        CFMutableSetRef _commonModes;   // 存放了UITrackingRunloopMode、NSDefaultRunloopMode 模式
        CFMutableSetRef _commonModeItems;
        CFRunloopModeRef _currentMode; //  当前模式
        CFMutableSetRef _modes;  //  装着可以用的所有模式,这个包含了 _commonModes
    }
    
    typedef struct __CFRunloopMode *CFRunloopModeRef;
    struct __CFRunloopMode {
        CFStringRef _name;
        CFMutableSetRef _source0;
        CFMutableSetRef _source1;
        CFMutableArrayRef _observers;
        CFMutableArrayRef _timers;
    }
    
    _modes 跟 commonModes的区别:
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        }];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    解释一下:
    NSDefaultRunloopMode、UITrackingRunloopMode才是真正的模式
    NSRunloopCommonModes并不是一个真正的模式,它只是一个标记
    NSRunLoopCommonModes 使timer能在commonModes数组中存放的模式下工作
    
    

    runloop循环机制

    1、通知observers进入loop ()
    2、通知observers即将开始处理Timers (NSTimer, performSelector:withObject:afterDelay:)
    3、通知observers开始处理Sources
    4、开始处理block
    5、开始处理source0 (触摸事件、performSelecter:onThread)
    6、如果存在source1则执行第八步 (基于port进行线程间的通信、系统事件捕捉)
    7、通知observers开始休眠(等待消息唤醒)
    8、通知observers结束休眠(被某个消息唤醒)
    处理timer事件
    处理GCD Async To Main Queue事件
    处理Source1
    9、处理blocks
    10、根据前面的执行结果决定怎么做
    1、回到第2步
    2、退出loop
    11、通知observer结束loop

    Observers、Source0、Source1、Timer

    Observers:
    用于监听runloop的状态
    UI刷新(BeforeWaiting)
    Autorelease Pool (BeforeWaiting)

    Timer:
    NSTimer
    performSelector:withObject:afterDelay:

    Source1:
    基于port进行线程间的通信
    系统事件捕捉

    Source0:
    触摸事件
    performSelector:onThread:

    runloop入口

    SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { 
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); // 通知observer进入runloop
        
        result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); // 通知observer处理事情
        
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); // 通知observer退出runloop
        
        return result;
    }
    

    __CFRunLoopRun 实现:

    
    CFRunloop.c
    static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { 
        // 通知observers 开始处理timers
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        
        // 通知observers开始处理sorces
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        
        // 开始处理blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 处理source0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
                // 处理blocks
                __CFRunLoopDoBlocks(rl, rlm);
        }
        
        // 判断有无source1
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                      // 如果有source1,就跳转到handle_msg
                    goto handle_msg;
                }
      
      // 通知observers开始休眠          
       __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
        
        // 等待别的消息来唤醒当前线程
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        // mach_msg可以让线程睡觉,这是内核层面的API,和while一直循环不一样。
        
     __CFRunLoopUnsetSleeping(rl);
     // 通知observers结束休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
    handle_msg:;
        // 唤醒runloop
        __CFRunLoopSetIgnoreWakeUps(rl);
        
        if(rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort){ //被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) || sourceHandledThisLoop;
        }
        
        // 处理blocks
        __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;
        }
        return retVal;
    }
    

    runloop是如何响应用户操作的,具体流程是什么?

    source1捕捉系统事件,点击屏幕事件在source1事件处理,source1吧事件包装成事件队列eventqueue,放到source0里面去处理

    相关文章

      网友评论

          本文标题:runloop原理

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