手把手带你探索Runloop

Runloop Objective-c语言中很多功能的幕后功臣,接下来我们就从以下几个方面来总结runloop:

  • Runloop是什么
  • Runloop的作用
  • Runloop和线程
  • Runloop的源码分析
  • 苹果利用Runloop实现的功能

Runloop是什么

官方是这样描述Runloop

  • runloop顾名思义它是处理消息循环的一个对象,直白点就是一种高级的do ... while循环,一般的循环会导致CPU进入一个忙等待的状态,而runloo则是"闲"等待
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 会去找对应的 Handler 处理事件。Runloop 可以让线程在需要做事的时候忙起来,不需要的话就让线程休眠。

Runloop的作用

  • 线程保活
  • 处理事件(触摸事件,performSelector,定时器)
  • 节省CPU消耗 (接受消息->等待->处理->接受消息...)

Runloop和线程

苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()

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;
}


CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}
// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
// 访问 __CFRunLoops 时的锁
static CFLock_t loopsLock = CFLockInit;

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(_CFThreadRef t) {
    //如果t为空,则默认赋值为主线程
    if (pthread_equal(t, kNilPthreadT)) {
    t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {
 // 第一次进入时,初始化全局__CFRunLoops,并先为主线程创建一个 RunLoop。
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated"
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
#pragma GCC diagnostic pop
        CFRelease(dict);
    }
    CFRelease(mainLoop);
    }
    CFRunLoopRef newLoop = NULL;
    //从__CFRunLoops中获取runloop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
    // 取不到时,创建一个
    newLoop = __CFRunLoopCreate(t);
        
        cf_trace(KDEBUG_EVENT_CFRL_LIFETIME|DBG_FUNC_START, newLoop, NULL, NULL, NULL);
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
    __CFUnlock(&loopsLock);
    // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
    if (newLoop) { CFRelease(newLoop); }
    // 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
#if _POSIX_THREADS
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
#else
            _CFSetTSD(__CFTSDKeyRunLoopCntr, 0, &__CFFinalizeRunLoop);
#endif
        }
    }
    return loop;
}

从上面的代码可以看出

  • 线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 CFMutableDictionaryRef 里
  • RunLoop在首次被线程获取时创建,在线程结束时销毁
  • 主线程Runloop会被默认启动,而子线程的Runloop需要手动启动

Runloop的源码分析

在Runloop源码中需要探索以下5个类:

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

CFRunloopRef

typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;

struct __CFRunLoop {
    CFRuntimeBase _base;
    _CFRecursiveMutex _lock;            /* locked for accessing mode list */
    __CFPort _wakeUpPort;           // used for CFRunLoopWakeUp 
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    _CFThreadRef _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;
    _Atomic(uint8_t) _fromTSD;
    Boolean _perCalloutARP;
    CFLock_t _timerTSRLock;
};
  • _wakeUpPort内核向该端口发送消息可以唤醒runloop
  • _pthread RunLoop对应的线程
  • _commonModes 存储的是字符串,记录所有标记为common的mode
  • _commonModeItems 存储所有commonMode的item(source、timer、observer)
  • _currentMode 当前运行的mode
  • _modes 存储的是CFRunLoopModeRef
  • _blocks_head doblocks的时候用到
__CFRunLoop结构图

一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

CFRunLoopModeRef

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    _CFRecursiveMutex _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
    __CFPort _timerPort;
    Boolean _mkTimerArmed;
#if TARGET_OS_WIN32
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};
image.png

一个CFRunLoopMode对象有一个name,N个source0、N个source1、timer、observer和port,可见事件都是由Mode在管理,而RunLoop管理Mode。

苹果官方给了5种mode

  • NSDefaultRunLoopMode(kCFRunloopDefaultMode):默认状态,app通常在这个mode下运行
  • UITrackingRunLoopMode:界面跟踪mode(例如滑动scrollview时不被其他mode影响
  • NSRunLoopCommonModes(kCFRunLoopCommonModes):是前两个mode的集合,可以把自定义mode用CFRunLoopAddCommonMode函数加入到集合中
  • GSEventReceiveRunLoopMode:接收系统内部mode,通常用不到
  • UIInitializationRunLoopMode:私有,只在app启动时使用,使用完就不在集合中了

CFRunLoopSourceRef

typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopSource * CFRunLoopSourceRef;

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    _CFRecursiveMutex _lock;
    CFIndex _order;         /* immutable */
    _Atomic uint64_t _signaledTime;
    CFMutableBagRef _runLoops;
    union {
    CFRunLoopSourceContext version0;    /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;   /* immutable, except invalidation */
    } _context;
};

CFRunLoopSourceRef是runloop的数据源抽象类对象(protocol),由源码可以看到共用体(union:在相同的内存位置存储不同的数据类型),可以理解为是事件产生的地方。Source有两个版本:Source0 和 Source1。

Source0

typedef struct {
    CFIndex version;
    void *  info;
    const void *(*retain)(const void *info);
    void    (*release)(const void *info);
    CFStringRef (*copyDescription)(const void *info);
    Boolean (*equal)(const void *info1, const void *info2);
    CFHashCode  (*hash)(const void *info);
    void    (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
    void    (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
    void    (*perform)(void *info);
} CFRunLoopSourceContext;

Source0处理App内部事件、APP自己负责管理(触发)例如:UIEvent CFSocket。 打断点基本都会看到它。

  • source0 只包含了一个回调(函数指针),它并不能主动触发事件。
  • CFRunLoopSourceSignal (source)将事件标记为待处理
  • CFRunLoopWakeUp(runloop) 手动调用,来唤醒Runloop,让其处理这个事件

自定义source实现步骤:
1.创建一个底层source0 源 CFRunLoopSourceRef source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);

  1. 把我们的创建的source0添加到runloop CFRunLoopAddSource(rlp, source0, kCFRunLoopDefaultMode)
  2. 执行信号,标记待处理CFRunLoopSourceSignal
  3. 唤醒runloop去处理CFRunLoopWakeUp
  4. 取消移除源CFRunLoopRemoveSource
  5. 释放runloopCFRelease(rlp)

Source1

typedef struct {
    CFIndex version;
    void *  info;
    const void *(*retain)(const void *info);
    void    (*release)(const void *info);
    CFStringRef (*copyDescription)(const void *info);
    Boolean (*equal)(const void *info1, const void *info2);
    CFHashCode  (*hash)(const void *info);
#if TARGET_OS_OSX || TARGET_OS_IPHONE
    mach_port_t (*getPort)(void *info);
    void *  (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
    void *  (*getPort)(void *info);
    void    (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;
  • Source1 包含了一个 mach_port 和一个回调(函数指针,被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。

CFRunLoopTimerRef

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;
    _CFRecursiveMutex _lock;
    CFRunLoopRef _runLoop;
    CFMutableSetRef _rlModes;
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;       /* immutable */
    CFTimeInterval _tolerance;          /* mutable */
    uint64_t _fireTSR;          /* TSR units */
    CFIndex _order;         /* immutable */
    CFRunLoopTimerCallBack _callout;    /* immutable */
    CFRunLoopTimerContext _context; /* immutable, except invalidation */
};

CFRunLoopTimerRef 是基于时间的触发器,它和 NSTimer 可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调

CFRunLoopObserverRef

struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    _CFRecursiveMutex _lock;
    CFRunLoopRef _runLoop;
    CFIndex _rlCount;
    CFOptionFlags _activities;      /* immutable */
    CFIndex _order;         /* immutable */
    CFRunLoopObserverCallBack _callout; /* immutable */
    CFRunLoopObserverContext _context;  /* immutable, except invalidation */
};

CFRunLoopObserverRef是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观察的时间点有这些

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), //即将进入Loop
    kCFRunLoopBeforeTimers = (1UL << 1), //即将处理Timer
    kCFRunLoopBeforeSources = (1UL << 2), //即将处理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6), // 刚唤醒
    kCFRunLoopExit = (1UL << 7), //即将退出Loop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

RunLoop内部的逻辑大致如下


RunLoop内部的逻辑

根据 [[NSRunLoop currentRunLoop] run]启动runloop作为入口分析源码的实现(由于代码太长只截取了关键部分代码)

// 用DefaultMode启动
void CFRunLoopRun(void) {   /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

// 用指定的Mode启动,允许设置RunLoop超时时间
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}

// RunLoop的实现
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    //通过modeName已经当前的runloop找到当前的Mode
    CFRunLoopModeRef currentMode = __CFRunLoopCopyMode(rl, modeName, false);
    //如果获取到的Model source,timer,observer为空则直接返回kCFRunLoopRunFinished
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
    if (currentMode) {
            __CFRunLoopModeUnlock(currentMode);
            CFRelease(currentMode);
        }
    __CFRunLoopUnlock(rl);
    return kCFRunLoopRunFinished;
    }

        // 1. 通知 Observers: RunLoop 即将进入 loop
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
        cf_trace(KDEBUG_EVENT_CFRL_RUN | DBG_FUNC_START, rl, currentMode, seconds, previousMode);
        //进入函数runloop内部
        result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
        cf_trace(KDEBUG_EVENT_CFRL_RUN | DBG_FUNC_END, rl, currentMode, seconds, previousMode);
        // 10. 通知 Observers: RunLoop 即将退出
        if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

        __CFRunLoopModeUnlock(currentMode);
        CFRelease(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
        rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}

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

    Boolean didDispatchPortLastTime = true;
    int32_t retVal = 0;
    do {
        __CFRunLoopUnsetIgnoreWakeUps(rl);

        if (rlm->_observerMask & kCFRunLoopBeforeTimers) {
            //2. 通知 Observers: RunLoop 即将触发 Timer 回调。
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        }
        
        if (rlm->_observerMask & kCFRunLoopBeforeSources) {
          // 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        }
        //执行被加入进来的Block
    __CFRunLoopDoBlocks(rl, rlm);
        //4. RunLoop 触发 Source0 (非port) 回调
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            // 执行被加入的block
            __CFRunLoopDoBlocks(rl, rlm);
        }

            msg = (mach_msg_header_t *)msg_buffer;
            // 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL, rl, rlm)) {
                goto handle_msg;
            }
#elif TARGET_OS_LINUX && !TARGET_OS_CYGWIN
            if (__CFRunLoopServiceFileDescriptors(CFPORTSET_NULL, dispatchPort, 0, &livePort)) {
                goto handle_msg;
            }
#elif TARGET_OS_WIN32 || TARGET_OS_CYGWIN
            if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
                goto handle_msg;
            }
#elif TARGET_OS_BSD
            if (__CFRunLoopServiceFileDescriptors(CFPORTSET_NULL, dispatchPort, 0, &livePort)) {
                goto handle_msg;
            }
#else
#error "invoking the port select implementation is required"
#endif
        }
#endif

        didDispatchPortLastTime = false;
        /// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)
    if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
    __CFRunLoopSetSleeping(rl);

        do {
            msg = (mach_msg_header_t *)msg_buffer;
            // 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
            // • 一个基于 port 的Source 的事件。
            // • 一个 Timer 到时间了
            // • RunLoop 自身的超时时间到了
            // • 被其他什么调用者手动唤醒
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy, rl, rlm);
            
            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);
        
        // user callouts now OK again
    __CFRunLoopUnsetSleeping(rl);
      // 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
    if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        //收到消息,处理消息
        handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);
        // 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
        else if (rlm->_timerPort != CFPORT_NULL && livePort == rlm->_timerPort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();            
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {         
                rlm->_timerSoftDeadline = UINT64_MAX;
                rlm->_timerHardDeadline = UINT64_MAX;
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
          // 9.2 如果有dispatch到main_queue的block,执行block。
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);


            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        }
#endif
        
        /* --- SOURCE1S  --- */
        
        else {
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            cf_trace(KDEBUG_EVENT_CFRL_DID_WAKEUP_FOR_SOURCE, rl, rlm, 0, 0);
            // Despite the name, this works for windows handles as well
            // 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
#if TARGET_OS_MAC
        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);
        }
#elif TARGET_OS_WIN32 || (TARGET_OS_LINUX && !TARGET_OS_CYGWIN)
                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
            } else {
                os_log_error(_CFOSLog(), "__CFRunLoopModeFindSourceForMachPort returned NULL for mode '%@' livePort: %u", rlm->_name, livePort);
            }
            
        }
        
        /* --- BLOCKS --- */
        
#if TARGET_OS_MAC
        if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
        // 执行加入到Loop的block
    __CFRunLoopDoBlocks(rl, rlm);
        
    if (sourceHandledThisLoop && stopAfterHandle) {
        // 进入loop时参数说处理完事件就返回。
        retVal = kCFRunLoopRunHandledSource;
        } else if (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)) {
         // source/timer/observer一个都没有了
        retVal = kCFRunLoopRunFinished;
    }
// 如果没超时,mode里没空,loop也没被停止,那继续loop。
    } while (0 == retVal);
#if __HAS_DISPATCH__
    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    }
#endif
    
    return retVal;
}

总结

  1. if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); 通知 Observers: RunLoop 即将进入 loop
  2. __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); 通知 Observers: RunLoop 即将触发 Timer 回调。
  3. __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
  4. Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); RunLoop 触发 Source0 (非port) 回调
  5. if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL, rl, rlm)) goto handle_msg; 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
  6. if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); __CFRunLoopSetSleeping(rl); 通知 Observers: RunLoop 的线程即将进入休眠(sleep)
  7. __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) mach_msg(msg, MACH_RCV_MSG, port); 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。1.一个基于 port 的Source 的事件。 2. 一个 Timer 到时间了 3. RunLoop 自身的超时时间到了 4.被其他什么调用者手动唤醒
  8. if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); 通知 Observers: RunLoop 的线程刚刚被唤醒了。
  9. 1 __CFRunLoopDoTimers(rl, rlm, mach_absolute_time()) 如果一个 Timer 到时间了,触发这个Timer的回调。
    2 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); 如果有dispatch到main_queue的block,执行block。
    3 CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort); 如果一个 Source1 (基于port) 发出事件了,处理这个事件
  10. if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); 通知 Observers: RunLoop 即将退出

苹果利用Runloop实现的功能

苹果利用Runloop实现的功能大致有以下功能:

  • 事件响应

当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 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的回调。

当有 UIGestureRecognizer 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。

  • AutoreleasePool

App启动后,主线程Runloop注册了两个Observers,其回调都是_wrapRunloopWithAutoreleasePoolHandler

Observers1 监视的事件是 Entry(即将进入Loop): 优先级最高,确保在所有的回调前创建释放池,回调内调用 _objc_autoreleasePoolPush()创建自动释放池

Observers2监听BeforeWaiting 和Exit事件: 优先级最低,保证在所有回调后释放释放池。BeforeWaiting事件:调用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush()释放旧池并创建新池,Exit事件: 调用_objc_autoreleasePoolPop(),释放自动释放池

在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

  • 界面更新

当在操作 UI 时,比如改变了 Frame、更新了 UIView/CALayer 的层次时,或者手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去。

苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件,回调去执行一个函数:
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。这个函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。

  • 定时器

NSTimer 其实就是 CFRunLoopTimerRef。一个 NSTimer 注册到 RunLoop 后,RunLoop 会为其重复的时间点注册好事件。例如 10:00, 10:10, 10:20 这几个时间点。RunLoop为了节省资源,并不会在非常准确的时间点回调这个Timer。Timer 有个属性叫做 Tolerance (宽容度),标示了当时间点到后,容许有多少最大误差。

如果某个时间点被错过了,例如执行了一个很长的任务,则那个时间点的回调也会跳过去,不会延后执行。就比如等公交,如果 10:10 时我忙着玩手机错过了那个点的公交,那我只能等 10:20 这一趟了。

CADisplayLink 是一个和屏幕刷新率一致的定时器(但实际实现原理更复杂,和 NSTimer 并不一样,其内部实际是操作了一个 Source)。如果在两次屏幕刷新之间执行了一个长任务,那其中就会有一帧被跳过去(和 NSTimer 相似),造成界面卡顿的感觉。在快速滑动TableView时,即使一帧的卡顿也会让用户有所察觉。

  • PerformSelecter

当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。

当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效

  • GCD

dispatch_async(dispatch_get_main_queue)使用到了RunLoop

libDispatch向主线程的Runloop发送消息将其唤醒,并从消息中取得block,并在回调__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()里执行这个block

  • NSURLConnection
image.png

通常使用 NSURLConnection 时,你会传入一个 Delegate,当调用了 [connection start] 后,这个 Delegate 就会不停收到事件回调。实际上,start 这个函数的内部会会获取 CurrentRunLoop,然后在其中的 DefaultMode 添加了4个 Source0 (即需要手动触发的Source)。CFMultiplexerSource 是负责各种 Delegate 回调的,CFHTTPCookieStorage 是处理各种 Cookie 的。

当开始网络传输时,我们可以看到 NSURLConnection 创建了两个新线程:com.apple.NSURLConnectionLoadercom.apple.CFSocket.private。其中 CFSocket 线程是处理底层 socket 连接的。NSURLConnectionLoader 这个线程内部会使用 RunLoop 来接收底层 socket 的事件,并通过之前添加的 Source0 通知到上层的 Delegate。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 前言 文章主要会RunLoop源码进行剖析,里面会有对它的理解及注释,有不足望见解 1,RunLoop是什么? 广...
    MoShengLive阅读 9,807评论 0 4
  • ##RunLoop浅析 参考文章:`https://www.jianshu.com/p/c38b5741919b`...
    星空梦想阅读 2,972评论 0 0
  • # Runloop 是什么? *字面意思:运行循环,程序运行过程中循环地处理事情 *实际上是:实际上是个对象,这个...
    胖子帅阅读 1,607评论 0 0
  • 一、邂逅runLoop 应该是一个美丽的下午,在一场面试上,遇见了runLoop,可惜擦肩而过。。。 二、认识ru...
    AlvinCrash阅读 3,025评论 0 0
  • 之前一直对ios的RunLoop机制一知半解,很多地方不是很清楚于是每次想到这个问题都会纠结,想搞明白这里边到底做...
    箫声乱阅读 3,989评论 0 4

友情链接更多精彩内容