RunLoop

作者: 只写Bug程序猿 | 来源:发表于2020-11-20 16:16 被阅读0次

    RunLoop简介

    RunLoop,就是一个运行循环,通过一个内部的运行循环(Event Loop)对事件或者消息管理的一个对象
    他是通过一个 do while循环来保持一致运行的(main函数不会退出的原因),然后通过sleep机制来降低CPU的占用问题
    特点:

    • 有消息要处理时,唤醒RunLoop来处理事件
    • 没有消息时,进行休眠,避免占用太多资源
    • 和线程是一一对应的
    void CFRunLoopRun(void) {    /* DOES CALLOUT */
        int32_t result;
        do {
            result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
            CHECK_FOR_FORK();
        } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
    }
    

    RunLoop 和线程的关系

    苹果不允许我们直接创建RunLoop,提供了两个获取的函数:CFRunLoopGetMainCFRunLoopGetCurrent,
    RunLoop和线程一一对应
    只能操作当前线程的RunLoop,不能操作其他线程
    RunLoop在第一次获取时创建([NSRunLoop current]),在线程结束时销毁
    源码解析

    CFRunLoopRef CFRunLoopGetMain(void) {
        CHECK_FOR_FORK();
        static CFRunLoopRef __main = NULL; // no retain needed
        if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
        return __main;
    }
    //__CFRunLoops 是一个字典
    static CFMutableDictionaryRef __CFRunLoops = NULL;
    
    CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
        if (pthread_equal(t, kNilPthreadT)) {
            t = pthread_main_thread_np();
        }
        __CFLock(&loopsLock);
        if (!__CFRunLoops) {
            __CFUnlock(&loopsLock);
     CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
            
        // 进行绑定 dict[@"pthread_main_thread_np"] = mainLoop
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    //将dict保存在__CFRunLoops中,释放dict
           if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        CFRelease(mainLoop);
            __CFLock(&loopsLock);
        }
    //获取loop
        CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        __CFUnlock(&loopsLock);
    //如果没有获取到创建新的
       if (!loop) {
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
            __CFLock(&loopsLock);
    //获取loop
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    //如果没有获取到创建新的
        if (!loop) {
    //进行绑定
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
       __CFUnlock(&loopsLock);
        CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) {
            _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
            if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
                _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
            }
        }
        return loop;
    }
    
    
    • 如果不存在__CFRunLoops,创建要一个CFMutableDictionaryRef
    • 根据当前线程创建一个CFRunLoopRef
    • 通过kvc将线程做为key,loop作为value存入字典,简历对应关系

    RunLoop结构,如何创建

    刚才研究RunLoop和线程的关系,可以看出RunLoop是通过__CFRunLoopCreate ()方法创建的,来看下他的实现

    static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
        CFRunLoopRef loop = NULL;
        CFRunLoopModeRef rlm;
        uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
        loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, CFRunLoopGetTypeID(), size, NULL);
        .....
      return loop;
    }
    
    
    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;
    };
    struct __CFRunLoopMode {
        CFRuntimeBase _base;
        pthread_mutex_t _lock;    /* must have the run loop locked before locking this */
        CFStringRef _name;
        Boolean _stopped;
        char _padding[3];
        CFMutableSetRef _sources0;
        CFMutableSetRef _sources1;
        CFMutableArrayRef _observers;
        CFMutableArrayRef _timers;
        CFMutableDictionaryRef _portToV1SourceMap;
        __CFPortSet _portSet;
        CFIndex _observerMask;
    #if USE_DISPATCH_SOURCE_FOR_TIMERS
        dispatch_source_t _timerSource;
        dispatch_queue_t _queue;
        Boolean _timerFired; // set to true by the source when a timer has fired
        Boolean _dispatchTimerArmed;
    #endif
    #if USE_MK_TIMER_TOO
        mach_port_t _timerPort;
        Boolean _mkTimerArmed;
    #endif
        uint64_t _timerSoftDeadline; /* TSR */
        uint64_t _timerHardDeadline; /* TSR */
    };
    
    • RunLoop是一个CFRunLoopRef对象.有_pthread,_commonModes,_commonModeItems,等属性
    • __CFRunLoopMode,有_sources0, _sources1, _observers,_timers等属性
    • RunLoop跟线程是一对一绑定关系
    • RunLoop 有多个CFRunLoopMode
    • 一个CFRunLoopMode有多个CFRunLoopSource CFRunLoopTimer CFRunLoopObserver
      RunLoop结构

    Timer,Source,Block等是如何加入到RunLoop中并且执行的

    我们在使用Timer时往往会遇到一些问题,比如timer运行时我们滑动界面,这个时候timer就会停止运行,因为timer的运行是依赖于RunLoop的,创建出来默认是在defaultModel中的,当我们滑动界面时Model就会从defaultModel变为UITrackingRunLoopMode,Model只能存在一个所以这个时候 Timer暂停运行,这个时候我们往往会这样写

    NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"fire in home -- %@",[[NSRunLoop currentRunLoop] currentMode]);
        }];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode: UITrackingRunLoopMode];
    

    那么[[NSRunLoop currentRunLoop] addTimer:timer forMode: UITrackingRunLoopMode];到底干了什么呢

    我们在Timer的block中打个断点,然后看下他的调用堆栈,看下他的执行流程

    (lldb) bt
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
      * frame #0: 0x0000000106c978bc 03-Runloop`__27-[ViewController timerDemo]_block_invoke(.block_descriptor=0x0000000106c9a0f0, timer=0x000060000002c780) at ViewController.m:50:38
        frame #1: 0x00007fff20871584 Foundation`__NSFireTimer + 67
        frame #2: 0x00007fff203a9112 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
        frame #3: 0x00007fff203a8be5 CoreFoundation`__CFRunLoopDoTimer + 926
        frame #4: 0x00007fff203a8198 CoreFoundation`__CFRunLoopDoTimers + 265
        frame #5: 0x00007fff203a2826 CoreFoundation`__CFRunLoopRun + 1949
        frame #6: 0x00007fff203a1b9e CoreFoundation`CFRunLoopRunSpecific + 567
        frame #7: 0x00007fff2b773db3 GraphicsServices`GSEventRunModal + 139
        frame #8: 0x00007fff24660af3 UIKitCore`-[UIApplication _run] + 912
        frame #9: 0x00007fff24665a04 UIKitCore`UIApplicationMain + 101
        frame #10: 0x0000000106c97d30 03-Runloop`main(argc=1, argv=0x00007ffee8f67be0) at main.m:15:16
        frame #11: 0x00007fff20257415 libdyld.dylib`start + 1
    (lldb) 
    

    可以看出调用流程是这样的:
    CFRunLoopRunSpecific->__CFRunLoopRun->__CFRunLoopDoTimers->__CFRunLoopDoTimer-> __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

    CFRunLoopRunSpecific
    SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
        CHECK_FOR_FORK();
       if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
       result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
      if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
       return result;
    }
    
    • __CFRunLoopDoObservers告诉RunLoop要开始run
    • __CFRunLoopRun
    • __CFRunLoopDoObservers告诉RunLoop 退出了
    __CFRunLoopRun
    static int32_t __CFRunLoopRun__CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    //  ....
    do {
    //通知observers即将处理timer事件
     if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
    //通知observers即将处理Sources事件
     if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
    //处理block
    __CFRunLoopDoBlocks(rl, rlm);
    //处理source0
     Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
    //处理sources0返回为YES
    if (sourceHandledThisLoop) {
    //处理block
                __CFRunLoopDoBlocks(rl, rlm);
        }
    /// 判断有无端口消息(Source1)
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                /// 处理消息
                goto handle_msg;
            }
    /// 通知 Observers: 即将进入休眠
    if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
    __CFRunLoopSetSleeping(rl);
    ///等待被唤醒
     __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
    /// 通知 Observers: 被唤醒,结束休眠 (系统级别的通知实现)
    if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
    ...
     handle_msg:
     if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
    ///如果被timer唤醒
          CFRUNLOOP_WAKEUP_FOR_TIMER();
           __CFRunLoopDoTimers(rl, rlm, mach_absolute_time()
       }else if (livePort == dispatchPort) {
    ///如果被GCD唤醒
           CFRUNLOOP_WAKEUP_FOR_DISPATCH();
           __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
         }else {
    //如果被source1处理
           CFRUNLOOP_WAKEUP_FOR_SOURCE();
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
        }
      }while (0 == retVal);
    
    __CFRunLoopDoTimers
    // rl and rlm are locked on entry and exit
    static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) {    /* DOES CALLOUT */
        Boolean timerHandled = false;
        CFMutableArrayRef timers = NULL;
        for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
            CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);
            
            if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {
                if (rlt->_fireTSR <= limitTSR) {
                    if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
                    CFArrayAppendValue(timers, rlt);
                }
            }
        }
        
        for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
            CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
            Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
            timerHandled = timerHandled || did;
        }
        if (timers) CFRelease(timers);
        return timerHandled;
    }
    
    • 通过循环遍历,然后判断__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)
    • CFArrayAppendValue(timers, rlt)timer添加到timers
    • 然后进行遍历,调用 __CFRunLoopDoTimer
    __CFRunLoopDoTimer
    static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt) { 
         __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);
    }
    
    • __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__进行回调,行程闭环
      依次类推,Source,Observer ,Block 原理大同小异
      RunLoop 主要处理item 处理负责休眠的事物
    如何创建一个常驻线程
    1. 为当前线程创建一个RunLoop
    2. 像当前RunLoop中添加一个port或者source来维持RunLoop事件循环
    3. 启动RunLoop
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    [runLoop run];
    
    总结

    RunLoop原理:

      1. 通知observer即将进入loop
      1. 通知Observer,即将处理timer
      1. 通知Observer,即将处理Source0
      1. 处理Source0
      1. 如果有Source1,跳至第九步
      1. 通知Observer,线程即将休眠
      1. 休眠等待唤醒
      1. 通知Observer,线程被唤醒
      1. 处理唤醒时收到的信息,之后跳回2
      1. 通知Observer,即将退出loop
    runtime运行逻辑

    RunLoop的item (6大事件)

    • block调用 : __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
    • timer调用 :__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
    • 响应source0 :__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
    • 响应source1:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
    • GCD主队列:__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
    • observer源:__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

    source0和source1 的区别

    source0: 用户点击等事件,按钮点击,触摸等事件,无法唤醒RunLoop
    source1:系统端口信息,基于port,可以直接唤醒loop

    ** CFRunLoopModem五种模式**

    kCFRunLoopDefaultMode:默认模式,主线程是在这个运行模式下运行

    UITrackingRunLoopMode:跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)

    UIInitializationRunLoopMode:在刚启动App时第进入的第一个 Mode,启动完成后就不再使用

    GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到

    kCFRunLoopCommonModes:伪模式,不是一种真正的运行模式,是同步Source/Timer/Observer到多个Mode中的一种解决方案

        kCFRunLoopEntry = (1UL << 0),
        kCFRunLoopBeforeTimers = (1UL << 1),
        kCFRunLoopBeforeSources = (1UL << 2),
        kCFRunLoopBeforeWaiting = (1UL << 5),
        kCFRunLoopAfterWaiting = (1UL << 6),
        kCFRunLoopExit = (1UL << 7),
        kCFRunLoopAllActivities = 0x0FFFFFFFU
    

    相关文章

      网友评论

          本文标题:RunLoop

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