iOS

iOS - RunLoop

2021-03-29  本文已影响0人  ienos

参考链接

一、RunLoop

目的是让线程在有工作的时候忙,在没有工作时让线程进入睡眠状态

所以一个 RunLoop 对象和 Thread 一一对应

本质是一个事件处理的循环

二、主线程 & 辅助线程

  1. 每个线程都会有一个关联的 RunLoop 对象
  2. 主线程在应用程序启动时,就会在主线程上添加 RunLoop 并运行
  3. 在辅助线程中,需要主动调用 run() 开启循环

CFRunLoop 源码中,

_CFRunLoopGet0(pthread_t t) 方法,可以通过线程获取 RunLoop

从下面的源码

  1. __CFRunLoops 是一个字典,用来保存 ThreadRunLoop 的对应关系
  2. __CFRunLoops 的创建,同时对 Main ThreadMain RunLoop 对应关系的保存
  3. __CFRunLoopsThread Pointer 作为 keyRunLoop 作为 value
/// __CFRunLoops
static CFMutableDictionaryRef __CFRunLoops = NULL;

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    /* NilPthreadT */
    if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
    }
    __CFSpinLock(&loopsLock);
    
    // __CFRunLoops 懒加载
    if (!__CFRunLoops) {
        __CFSpinUnlock(&loopsLock);
        
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        
        // 创建 MainThread 的 RunLoop
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        
        // 保存 MainThread 指针作为 key,RunLoop 为 value 
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        
        CFRelease(mainLoop);
        
        __CFSpinLock(&loopsLock);
        
    }
    
    // 通过 Thread 指针获取 RunLoop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFSpinUnlock(&loopsLock);
    
    /* Create RunLoop */
    if (!loop) {
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFSpinLock(&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
        __CFSpinUnlock(&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;
}

三、RunLoop 的运行结构

1. Source

RunLoop 接收来自两种事件的源

>> Input Sources

~~ 通常来自其他线程或另外的应用程序的异步事件,异步事件处理,如果实现 runUntilDate: RunLoop 会到点退出

[1] Port-based(Mach Ports)

[2] Custom Input Source

必须手动在其他线程添加

Perform Selector Sources

一种自定义的 Custom Imput Source,在执行完毕之后会被移除

>> Timer Sources

~~ 计时器有一次或者重复发生事件,重复计时器会根据计划的触发时间(而不是实际的触发时间)自动重新计划自己的时间;例如如果将计时器在特定时间触发,并且此后每 5 秒钟触发一次,则即使实际触发时间被延迟,计划的触发时间也将始终落在原始的 5 秒时间间隔上。如果触发时间延迟得太多,以致于错过了一个或多个计划的触发时间,则计时器将在错过的时间段内触发一次。在错过了一段时间后触发,计时器将重新安排为下一个计划的触发时间

image.png

2. Observer

注册 RunLoop.Observer 能够接收 RunLoop 的处理进程,在 RunLoop 循环本身执行期间的特定位置触发

3. RunLoopMode

是 Input Sources、Timers、Observers 的集合

下面是已定义的 Mode:

Mode Type Description
NSDefaultRunLoopMode Default 默认模式
NSConnectionReplyMode Connection 关联 NSConnection 对象
NSModalPanelRunLoopMode Modal 面板事件
NSEventTrackingRunLoopMode Event tracking 鼠标拖拽或者其他用户界面跟踪循环
NSRunLoopCommonMode Common modes mode group; 默认包括 default mode 和 tracking mode

也可通过 CFRunLoopAddCommonMode 新建自定义 Mode

4. RunLoop Run

指定一个 Mode Run

4. RunLoop

NSRunLoop 底层由 CFRunLoopRef 实现

5. RunLoop 的整个运行过程

  1. 通知 Observer 已进入 RunLoop
  2. 通知 Observer,Timer 即将触发
  3. 通知 Observer,不基于端口的 Input Source(Source0) 即将触发
  4. 触发所有准备触发的非基于端口的 Input Source(Source0)
  5. 如果基于端口的 Input Souce(Source1) 已准备好并等待启动,请立即处理事件,转到 9
  6. 通知 Observer,线程即将进入睡眠状态
  7. 使线程进入睡眠状态,直到发生以下事件之一
    - 时间到达基于端口的 input Source
    - 计时器触发
    - 为了运行循环设置的超时值到期
    - RunLoop 被指定唤醒
  8. 通知 Observer,线程刚刚醒来
  9. 处理未解决的事件
    - 如果触发了用户定义的计时器,处理计时器并重启 RunLoop,转 2
    - 如果触发了输入源,则传递事件
    - 如果 RunLoop 被明确唤醒,但未超时,请重启 RunLoop,转 2
  10. 通知 Observer,RunLoop 已退出
image.png

二、分析 CFRunLoop 源代码

源码中对 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI 定义,暂时找不到解释

#if DEPLOYMENT_TARGET_MACOSX
#define USE_DISPATCH_SOURCE_FOR_TIMERS 1
#define USE_MK_TIMER_TOO 1
#else
#define USE_DISPATCH_SOURCE_FOR_TIMERS 0
#define USE_MK_TIMER_TOO 1
#endif

DEPLOYMENT_TARGET_WINDOWS 为 Window 系统,删除对相关源代码

1 . 首先我们来看下 CFRunLoopRun 函数,可以看出默认是在 Default Mode 下运行,当 CFRunLoopRunResultstoppedfinished

void CFRunLoopRun(void) {   /* DOES CALLOUT */

    int32_t result;

    do {

        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);

        CHECK_FOR_FORK();

    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);

}

result 保存的类型为 CFRunLoopRunResult

typedef CF_ENUM(SInt32, CFRunLoopRunResult) {
    kCFRunLoopRunFinished = 1,
    kCFRunLoopRunStopped = 2,
    kCFRunLoopRunTimedOut = 3,
    kCFRunLoopRunHandledSource = 4
};
  1. CFRunLoopRunSpecific 可以看出 RunLoop 需要指定 Mode
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */

    ...

   /// 1. 通知 Observer 已进入 RunLoop - __CFRunLoopDoObservers: Entry
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
    /* __CFRunLoopRun */
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    /// 10. 通知 Observer,RunLoop 已退出 - __CFRunLoopDoObservers: Exit
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    ...
    
    return result;
}
  1. __CFRunLoopRun ,分为几个部分
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {

  /// 初始化一个定时器
  dispatch_source_t timeout_timer = NULL;

  ...

  // retVal: CFRunLoopRunResult
  int32_t retVal = 0;

  do {
    
        ...      

        /** __CFRunLoopDoObservers: Before Timers */
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        /** __CFRunLoopDoObservers: Before Sources */
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

        __CFRunLoopDoBlocks(rl, rlm);

        /** __CFRunLoopDoSources0 */
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
    
        ...      

        // 2. 如果有 Source1 跳转第 9 步, 处理来自主线程的端口消息
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
           msg = (mach_msg_header_t *)msg_buffer;
           if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {    
               goto handle_msg;
           }
        }

        ...

        /** __CFRunLoopDoObservers: Before Waiting */
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);

        /** 等待被唤醒 */
  
        ...
        
        /** After waiting */
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

         // 9.

  } while (0 == retVal)

  return retVal;
}

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {

  /// second 是举例 RunLoop 运行到结束的时间间隔
  /// 初始化一个定时器
  dispatch_source_t timeout_timer = NULL;

  ...

  dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);
  timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
  dispatch_retain(timeout_timer);
  
  timeout_context->ds = timeout_timer;
  timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
  timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
  
  dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
  
  /// 设置定时器触发事件处理回调 __CFRunLoopTimeout 是一个函数指针,通过 RunLoop.wakeUpPort 唤醒
  dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
  /// 设置定时器取消事件回调处理,__CFRunLoopTimeoutCancel 是一个函数指针,对 RunLoop 的释放
  dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);


  ...

}

等待被唤醒

{
    do {
        
        if (kCFUseCollectableAllocator) {
            objc_clear_stack(0);
            memset(msg_buffer, 0, sizeof(msg_buffer));
        }
        
        msg = (mach_msg_header_t *)msg_buffer;
        
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);

        if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            
            // Drain the internal queue.
            // If one of the callout blocks sets the timerFired flag, break out and service the timer.
            
            while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
            
            if (rlm->_timerFired) {

                // Leave livePort as the queue port, and service timers below
                rlm->_timerFired = false;
                break;

            } else {
                if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
            }
            
        } else {
            // Go ahead and leave the inner loop.
            break;
        }
        
    } while (1);
    
}

__CFRunLoopServiceMachPort

等待接收消息

static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port,
                                          mach_msg_header_t **buffer,
                                          size_t buffer_size,
                                          mach_port_t *livePort,
                                          mach_msg_timeout_t timeout) {

    Boolean originalBuffer = true;
    kern_return_t ret = KERN_SUCCESS;

    for (;;) {      /* In that sleep of death what nightmares may come ... */
        
        mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
        msg->msgh_bits = 0;
        msg->msgh_local_port = port;
        msg->msgh_remote_port = MACH_PORT_NULL;
        msg->msgh_size = buffer_size;
        msg->msgh_id = 0;
        
        if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
        
        ret = mach_msg(
                       msg,
                       MACH_RCV_MSG| // 接收消息
                       MACH_RCV_LARGE| // 接收消息最大值限制
                       ((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)| // 超时
                       MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)| 
                       MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV),
                       0,
                       msg->msgh_size,
                       port,
                       timeout,
                       MACH_PORT_NULL);
        
        CFRUNLOOP_WAKEUP(ret);
        
        // 接收到消息
        if (MACH_MSG_SUCCESS == ret) {
            *livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
            return true;
        }
        
        // 时间超时
        if (MACH_RCV_TIMED_OUT == ret) {
            if (!originalBuffer) free(msg);
            *buffer = NULL;
            *livePort = MACH_PORT_NULL;
            return false;
        }
        
        // 消息过大
        if (MACH_RCV_TOO_LARGE != ret) break;

        buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
        
        if (originalBuffer) *buffer = NULL;
        
        originalBuffer = false;
        *buffer = realloc(*buffer, buffer_size);
        
    }
    HALT;
    return false;
}

唤醒后处理事件

{   

    if (MACH_PORT_NULL == livePort) { /** No Port */

        CFRUNLOOP_WAKEUP_FOR_NOTHING();
        // handle nothing

    } else if (livePort == rl->_wakeUpPort) { /** WakeUp Port */

        CFRUNLOOP_WAKEUP_FOR_WAKEUP();
        // do nothing on Mac OS

    }else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) { /** DispatchQueue Port */

        // modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);

        CFRUNLOOP_WAKEUP_FOR_TIMER();
        // 处理 rlm -> queue 队列下的 GCD Timer
        if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
            // Re-arm the next timer, because we apparently fired early
            __CFArmNextTimerInMode(rlm, rl);
        }

    }else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { /** Timer Port */
        
        /// MK_Timer
        CFRUNLOOP_WAKEUP_FOR_TIMER();
        
        // On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
        // In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754

        // 处理 rlm -> queue 队列下的 GCD Timer
        if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
            // Re-arm the next timer
            __CFArmNextTimerInMode(rlm, rl);
        }

    }else if (livePort == dispatchPort) { /** DispathMainQueue Port */
        
        // 处理 GCD 主线程
        CFRUNLOOP_WAKEUP_FOR_DISPATCH();
        
        __CFRunLoopModeUnlock(rlm);
        __CFRunLoopUnlock(rl);
        _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
    
        __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        
        _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
        
        __CFRunLoopLock(rl);
        __CFRunLoopModeLock(rlm);
        sourceHandledThisLoop = true;
        didDispatchPortLastTime = true;
        
    } else {   /** Source1 */

        CFRUNLOOP_WAKEUP_FOR_SOURCE();
        
        // Despite the name, this works for windows handles as well
        CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
        
        if (rls) {
            
            mach_msg_header_t *reply = NULL;
            sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
            if (NULL != reply) {
                (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
                CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
            }
            
        }
    }
}

RunLoopMode

Mode Description
CFRuntimeBase _base ///
pthread_mutex_t _lock ///
CFStringRef _name ///
Boolean _stopped ///
char _padding[3] ///
CFMutableSetRef _sources0 No Base Port Input Source
CFMutableSetRef _sources1 Base Port Input Source
CFMutableArrayRef _observers Observers
CFMutableDictionaryRef _portToV1SourceMap Port and Source1 map
__CFPortSet _portSet 存储端口;TimerPort、wakeupPort、queuePort
CFIndex _observerMask ///
dispatch_source_t _timerSource Dispatch Timer Source
dispatch_queue_t _queue Run Loop Mode Queue (Handle Timer Source)
Boolean _timerFired Dispatch Timer Source 启动标志
Boolean _dispatchTimerArmed ///
mach_port_t _timerPort MK_Timer Port
Boolean _mkTimerArmed ///
uint64_t _timerSoftDeadline ///
uint64_t _timerHardDeadline ///
上一篇下一篇

猜你喜欢

热点阅读