iOS底层原理-RunLoop

  • 准备工做

    RunLoop源码下载地址
  • RunLoop 概念

    • RunLoop 简介
      RunLoop其实就是一种事务处理的循环,是事件接受、分发的机制的实现,用来不停的调度工做以及处理输入事件。其本质就是一个 do-while循环,若是有输入则执行对应事件,没有则休息,这里RunLoopdo-while和普通的do-while循环不同,普通的循环会让cpu处于忙等待的状况,当cpu跑满了也就崩溃了。而runloopdo-while循环是一种闲等待,也就是不会消耗cpu因此runloop具备休眠功能。
      下图是runloop运行原理图: 171000560455268.png
  • RunLoop 做用

    • 保持程序持续运行。程序启动就会自动开启一个runloop在主线程
    • 处理App中的各类事件
    • 节省CPU资源,提升程序性能 有事情则作事,没事情则休眠
  • RunLoop 和线程之间的关系

    从上文中的 runloop运行原理图能够知道二者之间的关系是一一对应的关系
  • RunLoop 底层源码分析

    • RunLoop 本质

      RunLoop底层其实就是一个结构体,是一个__CFRunLoop对象,具体结构以下: image.png 从对象的结构中能够得出当前运行的 mode只会有一个,可是每一个 runloop里面是存在多个 mode的同时也存在着多个 item也就是事务(source、timer、observer面试

    • 获取 RunLoop以及进一步验证RunLoop和线程之间的关系
      • 获取主线程runloop: CFRunLoopGetMain() image.png发现底层就是调用 _CFRunLoopGet0方法获取 runloop,入参是主线程
      • 获取当前线程runloop: CFRunLoopGetCurrent()
        image.png 一样的是调用了 _CFRunLoopGet0方法获取 runloop此时的入参是当前线程
      • 获取 runloop: _CFRunLoopGet0
        CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
           if (pthread_equal(t, kNilPthreadT)) {
               t = pthread_main_thread_np();
           }
           __CFSpinLock(&loopsLock);
           if (!__CFRunLoops) {
               //若是存储runloop的字典不存在则建立
               //只有第一次进来的时候才会走到这
               __CFSpinUnlock(&loopsLock);
        
        
               //建立一个临时字典
               CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        
               //建立主线程
               CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        
               // dict : key value
               //将线程和runloop以key-value的形式存到临时字典
               CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        
               //OSAtomicCompareAndSwapPtrBarrier  将临时的字典写入到__CFRunLoops
               if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
                   CFRelease(dict);
               }
        
               //释放mainLoop
               CFRelease(mainLoop);
               __CFSpinLock(&loopsLock);
           }
           //经过线程也就是key 到__CFRunLoops中寻找runloop
           CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
           __CFSpinUnlock(&loopsLock);
           if (!loop) {
               //若是没有找到则基于入参的线程建立一个runloop
               //这里只有多是获取其余线程的runloop
               CFRunLoopRef newLoop = __CFRunLoopCreate(t);
               __CFSpinLock(&loopsLock);
               loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
               if (!loop) {
                   //此时将新建立的runloop存到__CFRunLoops中
                   CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
                   //赋值操做
                   loop = newLoop;
               }
               // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
               __CFSpinUnlock(&loopsLock);
               //释放新建立的runloop
               CFRelease(newLoop);
           }
           if (pthread_equal(t, pthread_self())) {
               //若是传入线程就是当前线程
               _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
               if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
                   //注册一个回调,当线程销毁时,销毁对应的RunLoop
                   //由于主线程是一直伴随着程序运行的因此不须要注册这个回调
                   _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
               }
           }
           return loop;
        }
        复制代码
        大体流程以下:
        1. 先判断存储 runloop的字典是否存在也就是__CFRunLoops是否存在,若是不存在说明是第一次获取 runloop此时建立主线程 runloop而后存储到 __CFRunLoops
        2. 经过 key也就是入参的线程在 __CFRunLoops中查找 runloop若是找到了给对应的 runloop注册一个回调而后返回,若是没有找到则基于入参的线程建立一个新的 runloop而后存到 __CFRunLoops,而后同样的注册一个回调而后返回
        总结:
        从上述的源码发现底层 runloop的存储使用的是字典结构,线程是 key,对应的 runloopvalue,从这里进一步的印证了线程和 runloop的关系是一一对应的
    • RunLoop 建立

      image.png 就是开辟空间建立 runloop对象并配置对应参数数组

    • RunLoop Mode
      struct __CFRunLoopMode {
            CFRuntimeBase _base;
            pthread_mutex_t _lock;    /* must have the run loop locked before locking this */
            CFStringRef _name;
            Boolean _stopped; //mode是否被终止
            char _padding[3];
            //几种事件
            CFMutableSetRef _sources0;  //sources0 集合
            CFMutableSetRef _sources1;   //sources1 集合
            CFMutableArrayRef _observers; //observers 集合
            CFMutableArrayRef _timers;  //timers 集合
            CFMutableDictionaryRef _portToV1SourceMap;
            __CFPortSet _portSet;  //保存全部须要监听的port,好比_wakeUpPort,_timerPort都保存在这个数组中
            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
        #if DEPLOYMENT_TARGET_WINDOWS
            DWORD _msgQMask;
            void (*_msgPump)(void);
        #endif
            uint64_t _timerSoftDeadline; /* TSR */
            uint64_t _timerHardDeadline; /* TSR */
        };
      
      复制代码

      __CFRunLoopMode的结构体当中咱们能够获得的消息是每一个 mode中有若干个 source0、source一、timer、observer和若干个端口因此事务的执行是由 mode控制的,而上文张分析 runloop的结构的时候又知道每一个 runloop当中都含有多个 mode,可是当前的 mode只有一个,因此从这里能够总结出两点:markdown

      1. runloop管理着 mode
      2. 每次 runloop 运行的时候都必须指定有且仅有一个 mode指定对应的事务,若是须要执行其余 mode的事务须要切换 mode从新进入 runloop
      3. 这里能够总结出 runloop mode 事务、三者的关系以下图

      未命名文件(40).png
      Mode的类型
      从官方文档中能够知道 mode的类型一共五种以下 image.png 其中mode在苹果文档中说起的有五个,而在iOS中公开暴露出来的只有 NSDefaultRunLoopModeNSRunLoopCommonModesNSRunLoopCommonModes 其实是一个 Mode 的集合,默认包括 NSDefaultRunLoopModeNSEventTrackingRunLoopMode(当以模态方式跟踪事件(例如鼠标拖动循环)时,应将运行循环设置为此模式。)。可经过 CFRunLoopAddCommonMode添加到 runloop运行模式集合中app

    • RunLoop Source

      Run Loop Source分为Source、Observer、Timer三种,也就是 ModeItemasync

      1. Source source又分为 source0source1表示能够唤醒RunLoop的一些事件,例如用户点击了屏幕,就会建立一个RunLoop
        • source0 表示非系统事件,即用户自定义的事件,不能自动唤醒 runloop须要先调用CFRunLoopSourceSignal(source)source置为待处理事件,而后再唤醒 runloop让其处理这个事件
        • source1 由RunLoop和内核管理,source1带有mach_port_t,能够接收内核消息并触发回调,如下是source1的结构体
      2. Observer 主要用于监听RunLoop的状态变化,并做出必定响应,主要有如下一些状态 image.png
      3. Timer 就是是定时器,能够在设定的时间点抛出回调,

      综上所述得知
      Runloop 经过监控 Source 来决定有没有任务要作,除此以外,咱们还能够用 Runloop Observer 来监控 Runloop 自己的状态。 Runloop Observer 能够监控上面的 Runloop 事件,具体流程以下图。 image.pngide

    • 添加 mode
      • CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef mode) 添加一个 moderunloop的 commonmodes中 image.png
      • CFStringRef CFRunLoopCopyCurrentMode(CFRunLoopRef rl) 返回当前运行的 mode的名称 image.png 从这里也能够证实当前运行的 mode是惟一的
      • CFArrayRef CFRunLoopCopyAllModes(CFRunLoopRef rl) 返回 runloop中全部的 mode image.png
    • 添加/移除 Source
      • void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode) 添加 source0或者是 source1
        void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) {    /* DOES CALLOUT */
          CHECK_FOR_FORK();
          if (__CFRunLoopIsDeallocating(rl)) return;
          if (!__CFIsValid(rls)) return;
          Boolean doVer0Callout = false;
          __CFRunLoopLock(rl);
          if (modeName == kCFRunLoopCommonModes) {
              //若是是kCFRunLoopCommonModes
              //则将对应的source添加到_commonModeItems中
              CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
              if (NULL == rl->_commonModeItems) {
                  //若是没有则建立一个
                  rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
              }
              //
              CFSetAddValue(rl->_commonModeItems, rls);
              if (NULL != set) {
                  //若是set 存在则将source添加到每个common-modes中去
                  //这里须要解释一下 应为kCFRunLoopCommonModes 是Mode的集合  所需须要在每一个mode中都对应添加这个source
                  //这样才能体现kCFRunLoopCommonModes的灵活性 无论设置那个mode 只要改mode在kCFRunLoopCommonModes这个
                  //集合中source就能执行
                  CFTypeRef context[2] = {rl, rls};
                  /* add new item to all common-modes */
                  CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
                  CFRelease(set);
              }
          } else {
              //经过modeName 获取 mode
              CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
              if (NULL != rlm && NULL == rlm->_sources0) {
                  //若是mode存在可是_sources0不存在则初始化_sources0、_sources一、_portToV1SourceMap
                  rlm->_sources0 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
                  rlm->_sources1 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
                  rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL);
              }
              if (NULL != rlm && !CFSetContainsValue(rlm->_sources0, rls) && !CFSetContainsValue(rlm->_sources1, rls)) {
                  //若是_sources0和_sources1的集合中都不存在source
                  if (0 == rls->_context.version0.version) {
                      //若是version == 0 则添加到_sources0
                      CFSetAddValue(rlm->_sources0, rls);
                  } else if (1 == rls->_context.version0.version) {
                      //若是version == 1 则添加到_sources1
                      CFSetAddValue(rlm->_sources1, rls);
                      __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
                      if (CFPORT_NULL != src_port) {
                          //此处只有在加到source1的时候才会把souce和一个mach_port_t对应起来
                          //能够理解为,source1能够经过内核向其端口发送消息来主动唤醒runloop
                          CFDictionarySetValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls);
                          __CFPortSetInsert(src_port, rlm->_portSet);
                      }
                  }
                  __CFRunLoopSourceLock(rls);
                  //把runloop加入到source的_runLoops中
                  if (NULL == rls->_runLoops) {
                      rls->_runLoops = CFBagCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeBagCallBacks); // sources retain run loops!
                  }
                  CFBagAddValue(rls->_runLoops, rl);
                  __CFRunLoopSourceUnlock(rls);
                  if (0 == rls->_context.version0.version) {
                      if (NULL != rls->_context.version0.schedule) {
                          doVer0Callout = true;
                      }
                  }
              }
              if (NULL != rlm) {
                  __CFRunLoopModeUnlock(rlm);
              }
          }
          __CFRunLoopUnlock(rl);
          if (doVer0Callout) {
              // although it looses some protection for the source, we have no choice but
              // to do this after unlocking the run loop and mode locks, to avoid deadlocks
              // where the source wants to take a lock which is already held in another
              // thread which is itself waiting for a run loop/mode lock
              rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName);    /* CALLOUT */
          }
        }
        复制代码
        添加 source步骤
        1. 先判断 mode若是是 commonMode则将 source添加到 _commonModeItems中,而且将 source添加到每个 common-modes
        2. 若是不是 commonMode则先经过 mode名称获取对应 mode
        3. 判断 modesource0是否存在,不存在则初始化_sources0、_sources一、_portToV1SourceMap
        4. 判断 source是否已经在 source0或者是 source1的集合中
        5. 若是存在则啥都不干,不存在则判断 rls->_context.version0.version是0,则添加到 source0集合中
        6. 是1 则添加到 source1的集合中而后吧souce和一个mach_port_t对应起来
      • void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode)
        void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) {    /* DOES CALLOUT */
           CHECK_FOR_FORK();
           Boolean doVer0Callout = false, doRLSRelease = false;
           __CFRunLoopLock(rl);
           //一样的是须要判断mode
           if (modeName == kCFRunLoopCommonModes) {
               if (NULL != rl->_commonModeItems && CFSetContainsValue(rl->_commonModeItems, rls)) {
                   CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
                   //_commonModeItems中删除对应source
                   CFSetRemoveValue(rl->_commonModeItems, rls);
                   if (NULL != set) {
                       CFTypeRef context[2] = {rl, rls};
                       /* remove new item from all common-modes */
                       CFSetApplyFunction(set, (__CFRunLoopRemoveItemFromCommonModes), (void *)context);
                       CFRelease(set);
                   }
               } else {
               }
           } else {
               CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, false);
               if (NULL != rlm && ((NULL != rlm->_sources0 && CFSetContainsValue(rlm->_sources0, rls)) || (NULL != rlm->_sources1 && CFSetContainsValue(rlm->_sources1, rls)))) {
                   CFRetain(rls);
                   if (1 == rls->_context.version0.version) {
                       __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
                       if (CFPORT_NULL != src_port) {
                           CFDictionaryRemoveValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port);
                           __CFPortSetRemove(src_port, rlm->_portSet);
                       }
                   }
                   CFSetRemoveValue(rlm->_sources0, rls);
                   CFSetRemoveValue(rlm->_sources1, rls);
                   __CFRunLoopSourceLock(rls);
                   if (NULL != rls->_runLoops) {
                       CFBagRemoveValue(rls->_runLoops, rl);
                   }
                   __CFRunLoopSourceUnlock(rls);
                   if (0 == rls->_context.version0.version) {
                       if (NULL != rls->_context.version0.cancel) {
                           doVer0Callout = true;
                       }
                   }
                   doRLSRelease = true;
               }
               if (NULL != rlm) {
                   __CFRunLoopModeUnlock(rlm);
               }
           }
           __CFRunLoopUnlock(rl);
           if (doVer0Callout) {
               // although it looses some protection for the source, we have no choice but
               // to do this after unlocking the run loop and mode locks, to avoid deadlocks
               // where the source wants to take a lock which is already held in another
               // thread which is itself waiting for a run loop/mode lock
               rls->_context.version0.cancel(rls->_context.version0.info, rl, modeName);    /* CALLOUT */
           }
           if (doRLSRelease) CFRelease(rls);
        }
        复制代码
        理解了添加的操做,删除的操做就比较简单了,这里就不赘述了
      • void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode) image.png添加Observer和添加 source区别就在于 Observer若是已经添加到其余 runloop中去了则不能再被添加,从 __CFRunLoopObserver__CFRunLoopSource也能够看出差别点 image.png image.png 定义的时候__CFRunLoopObserver就是只能获取一个runloop__CFRunLoopSource是一个 runloop的集合
      • void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef * mode)
        CFRunLoopRemoveSource
      • void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode)
        CFRunLoopAddObserver
      • void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode)
        CFRunLoopRemoveSource
    • CFRunLoopRun

      image.png

    • CFRunLoopRunInMode

      指定模式下运行 runloop image.png 再看CFRunLoopRunSpecific方法函数

      SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
        CHECK_FOR_FORK();
        if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
        __CFRunLoopLock(rl);
        //根据modeName找到本次运行的mode
        CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
        //若是没找到 || mode中没有注册任何事件,则就此中止,不进入循环
        if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
            Boolean did = false;
            if (currentMode) __CFRunLoopModeUnlock(currentMode);
            __CFRunLoopUnlock(rl);
            return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
        }
        volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
        //取上一次运行的mode
        CFRunLoopModeRef previousMode = rl->_currentMode;
        //传入的mode置为当前mode
        rl->_currentMode = currentMode;
        int32_t result = kCFRunLoopRunFinished;
      
        // 1.通知observer即将进入runloop
        if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
        result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
        //通知observer已退出runloop
        if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
      
        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
        rl->_currentMode = previousMode;
        __CFRunLoopUnlock(rl);
        return result;
      }
      复制代码

      大体流程以下:oop

      1. 判断当前 runloop中是否含有 mode若是没有则退出
      2. 判断 mode中是否含有 itme没有则退出
      3. 将传入的 mode置为当前运行 mode
      4. 通知 Observer 进入 runloop
      5. 启动 runloop 这里调用的是 __CFRunLoopRun方法
      6. 通知 Observer 已退出 runloop
    • __CFRunLoopRun

      因为该源码太多这里就是用伪代码来代替源码分析

      //核心函数
        /* rl, rlm are locked on entrance and exit */
        static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode){
      
            //经过GCD开启一个定时器,而后开始跑圈
            dispatch_source_t timeout_timer = NULL;
            ...
            dispatch_resume(timeout_timer);
      
            int32_t retVal = 0;
      
            //处理事务,即处理items
            do {
      
                // 通知 Observers: 即将处理timer事件
                __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
      
                // 通知 Observers: 即将处理Source事件
                __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
      
                // 处理Blocks
                __CFRunLoopDoBlocks(rl, rlm);
      
                // 处理sources0
                Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
      
                // 处理sources0返回为YES
                if (sourceHandledThisLoop) {
                    // 处理Blocks
                    __CFRunLoopDoBlocks(rl, rlm);
                }
      
                // 判断有无故口消息(Source1)
                if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
                    // 处理消息
                    goto handle_msg;
                }
      
      
                // 通知 Observers: 即将进入休眠
                __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
                __CFRunLoopSetSleeping(rl);
      
                // 等待被唤醒
                __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
      
                // user callouts now OK again
                __CFRunLoopUnsetSleeping(rl);
      
                // 通知 Observers: 被唤醒,结束休眠
                __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
      
      
            handle_msg:
                if (被timer唤醒) {
                    // 处理Timers
                    __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
                }else if (被GCD唤醒){
                    // 处理gcd
                    __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
                }else if (被source1唤醒){
                    // 被Source1唤醒,处理Source1
                    __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
                }
      
                // 处理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;//结束
                }
      
      
      
            }while (0 == retVal);
      
            return retVal;
        }
      复制代码

      发现和上文中的 runloop流程图是一致的
      这里挑一个处理timers源码做讲解其余大同小异 __CFRunLoopDoTimers源码 image.png image.png 发现底层就是经过调用 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__方法来执行 timer的回调 image.png 这里也能够经过打印堆栈来验证 image.png 相似这种函数一共有6种:性能

      1. block应用:__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
      2. 调用timer:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
      3. 响应source0: __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
      4. 响应source1: __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
      5. GCD主队列:__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
      6. observer源: __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
    • 总结

      经过源码探索也能够发现 runloop其实很简单,它是一个对象,而且和线程一一对应,主线程的 runloop是自动开启的,子线程的 runloop是须要手动开启的,每个 runloop中会有多个 mode, 每个 mode中又有多个 source,可是 runloop每次 run只能选择一种 mode(这也就形成了 timer和页面滑动以后的问题下文有详细讲解)。

      RunLoop运行的核心是一个do..while..循环,遍历全部须要处理的事件,若是有事件处理就让线程工做,没有事件处理则让线程休眠,同时等待事件到来。

  • RunLoop的应用及相关问题

    • NSTimer的应用

      NSTimer其实就是上文提到的 timer,底层 runloop会在每一个时间点都注册一个事件,到了时间点回调时间,可是 runloop为了节省资源不会每次都很准确的回调事件,timer有个属性是宽容度 Tolerance标识了能够有的多大偏差,这样的机制就致使了 timer不是特别准确。并且若是某个时间点错过了就不会再重复执行
      面试题:以+scheduledTimerWithTimeInterval:的方式触发的timer,在滑动页面上的列表时,timer会暂停回调, 为何?如何解决?
      应为scheduledTimerWithTimeInterval添加方式默认的 modedefaultMode,而页面滑动时的 modeUITrackingRunLoopMode,滑动的时候 runloop会切换 mode,上文也提到过 runloop一次只能执行一个 mode中的事件,因此对应的 timer就会中止。
      **解决方法是:**将 timer添加到 NSRunLoopCommonModes中就好

    • GCD Timer的应用

      GCD的线程管理是经过系统来直接管理的。GCD Timer 是经过 dispatch portRunLoop 发送消息,来使 RunLoop 执行相应的 block,若是所在线程没有 RunLoop,那么 GCD 会临时建立一个线程去执行 block,执行完以后再销毁掉,所以 GCDTimer 是能够不依赖 RunLoop 的。
      至于这两个 Timer 的准确性问题,若是不在 RunLoop 的线程里面执行,那么只能使用 GCD Timer,因为 GCD Timer 是基于 MKTimer(mach kernel timer),已经很底层了,所以是很准确的。
      若是GCD TimerRunLoop的线程中执行那么可能出现的问题和 timer大同小异
      面试题:GCD Timer和 NSTimer那个更精确,为何
      这个问题不能单单直接确定的说是 GCD Timer上文也分析到了若是在有 RunLoop的线程中精确度和 NSTimer差很少,具体缘由能够看上文的 NSTimer分析
      若是线程中没有 runloop首选 GCD timer,它相对于 NSTimer更加低层而且没有 runloop的影响因此更加精确

    • AutoreleasePool和RunLoop的关系
      1. App 启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()。
      2. 第一个 Observer 监视的事件是 Entry(即将进入 Loop),其回调内会调用 _objc_autoreleasePoolPush() 建立自动释放池。其 order 是 -2147483647,优先级最高,保证建立释放池发生在其余全部回调以前。
      3. 第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用 _objc_autoreleasePoolPop()_objc_autoreleasePoolPush() 释放旧的池并建立新池;Exit(即将退出 Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observerorder 是 2147483647,优先级最低,保证其释放池子发生在其余全部回调以后。

      总结一下自动释放池的建立、销毁时机:

      1. kCFRunLoopEntry 进入runloop以前,建立一个自动释放池
      2. kCFRunLoopBeforeWaiting 休眠以前,销毁自动释放池,建立一个新的自动释放池
      3. kCFRunLoopExit 退出runloop以前,销毁自动释放池
    • 事件响应

      苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收。这个过程的详细状况能够参考这里。SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给须要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。一般事件好比 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。

    • 手势识别

      当上面的 _UIApplicationHandleEventQueue() 识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver(),其内部会获取全部刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。

    • GCD和RunLoop的关系

      在RunLoop的源代码中能够看到用到了GCD的相关内容,可是RunLoop自己和GCD并无直接的关系。当调用了dispatch_async(dispatch_get_main_queue(), <#^(void)block#>)时libDispatch会向主线程RunLoop发送消息唤醒RunLoop,RunLoop从消息中获取block,而且在__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__回调里执行这个block。不过这个操做仅限于主线程,其余线程dispatch操做是所有由libDispatch驱动的。

    • 图片下载中RunLoop的应用

      因为图片渲染到屏幕须要消耗较多资源,为了提升用户体验,当用户滚动Tableview的时候,只在后台下载图片,可是不显示图片,当用户停下来的时候才显示图片。 [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"imgName"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]]; 此方法即时此时正在滑动屏幕也不会出现卡顿的状况,应为滑动的过程当中,runloopmodeNSEventTrackingRunLoopMode因此滑动的时候会切换 mode那么 NSDefaultRunLoopMode的事件也就中止了, 滑动完成以后才会切到 NSDefaultRunLoopMode而后继续事件,这样就不会形成卡顿现象

    • 线程常驻

      这个就很简单了其实就是开个线程将 runloop跑起来,可是单纯的跑 runloop也不行,若是事件执行完了 runloop就会消亡对应的线程也就会销毁。因此能够添加一个 timer或者是 NSPort 具体方法以下:

      1. [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
      2. [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
      3. [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];

      三种方法都能保证 runloop运行,线程常驻

相关文章
相关标签/搜索