老青菜

iOS RunLoop 底层分析

2019-10-15

字面上理解,RunLoop是一个运行循环,和javascript里的eventloop类似,保证了线程一直在工作,有任务的时候处理任务,没有任务的时候休眠,节省资源。
本质上还是一个循环,类似下面的代码。

do {
   //获取消息
   //处理消息
} while (!退出)

底层架构

CoreFoundation是开源的,我们可以找到CFRunLoop实现
RunLoop主要包含5个类。

CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef 

他们之间的关系如下(网图):

可以很清楚的看到,一个RunLoop下面可以有多个Mode,一个Mode包含SourceObserverTimer。接下来我们看看具体的实现:

CFRunLoopRef & CFRunLoopModeRef

typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
     // 用来唤醒RunLoop的端口,接收消息,执行CFRunLoopWakeUp方法
    __CFPort _wakeUpPort;
    //被标记为CommonModes的Mode集合  
    CFMutableSetRef _commonModes; 
    //被标记CommonModes的items 集合    
    CFMutableSetRef _commonModeItems;
    //RunLoop 当前运行的 Mode
    CFRunLoopModeRef _currentMode;
    //modes,CFRunLoopModeRef列表  
    CFMutableSetRef _modes;           
    //此处省略...
};

typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
     //mode 名字, 例如 kCFRunLoopDefaultMode
    CFStringRef _name;
    //source0 ,CFRunLoopSource集合       
    CFMutableSetRef _sources0;
    //source1,CFRunLoopSource集合    
    CFMutableSetRef _sources1;   
    //observer,CFRunLoopObserverRef数组 
    CFMutableArrayRef _observers; 
    //timers,CFRunLoopTimerRef数组
    CFMutableArrayRef _timers;
    //此处省略...
};

从源码可以看出,RunLoopMode管理了Sourceobservertimer事件,RunLoop管理了若干个RunLoopMode

这里需要注意,RunLoop一次只能运行在一个mode下,如果需要切换mode,只能退出loop,重新run

CFRunLoopSource

CFRunLoopSourceRef是事件源,包含Source0Source1,查看源码。

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 {
        //source0
        CFRunLoopSourceContext version0;    /* immutable, except invalidation */
        //source1
        CFRunLoopSourceContext1 version1;    /* immutable, except invalidation */
    } _context;
};

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

//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 端口
    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;

不难看出,source1source0多了一个mach_port_t端口用来接收消息,这样source1就可以直接响应。这里总结一下:

  1. Source0,处理App内部事件,App自己负责管理(触发),如UIEvent、CFSocket。 和mach port无关,并不能主动触发事件。使用时,需要先调用CFRunLoopSourceSignal(source),将这个Source标记为待处理,然后手动调用CFRunLoopWakeUp(RunLoop)来唤醒RunLoop,让其处理这个事件。
  2. Source1,由RunLoop和内核管理,mach port驱动,如CFMachPortCFMessagePort。 包含了mach_port,被用于通过内核和其他线程相互发送消息,可以主动唤醒RunLoop

CFRunLoopObserver

CFRunLoopObserverRef是一个监听器,每个Observer都包含了一个回调函数的指针,当 RunLoop的状态发生变化时,观察者就能通过回调接受到这个变化。

typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopObserver * 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 */
};

//具体的状态
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), // 进入RunLoop 
    kCFRunLoopBeforeTimers = (1UL << 1), // 即将开始Timer处理
    kCFRunLoopBeforeSources = (1UL << 2), // 即将开始Source处理
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6), //从休眠状态唤醒
    kCFRunLoopExit = (1UL << 7), //退出RunLoop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

如何运行

当我们RunLoop.run(),系统到底做了什么了?我们来看看源码:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName
, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    //此处省略部分代码
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    //此处省略部分代码
    return result;
}
void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } 
    while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, 
CFTimeInterval seconds, Boolean stopAfterHandle
, CFRunLoopModeRef previousMode) {
    // 通知即将进入RunLoop
    __CFRunLoopDoObservers(KCFRunLoopEntry);
    do {
        // 1. RunLoop 即将处理 Timers, 通知 observers
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) 
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        // 2. RunLoop 即将处理 Sources,通知 observers
        if (rlm->_observerMask & kCFRunLoopBeforeSources) 
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        // 3. RunLoop 开始处理 Source0事件,比如说Touch事件
        // sourceHandledThisLoop 是否处理完Source0事件
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            // 处理完Source0之后的回调
            __CFRunLoopDoBlocks(rl, rlm);
        }
        // 处理完source0事件,且没有超时 poll 为false, 
        // 没有处理完source0 事件,或者超时,为true
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        // didDispatchPortLastTime 初始化为true,即第一次循环的时候不会走if方法,
        // 4. 消息处理,source1 事件,此处跳过休眠,直接到下面代码的第8步
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
            // 从消息缓冲区获取消息
            msg = (mach_msg_header_t *)msg_buffer;
            // dispatchPort收到消息,立刻去处理 
            // dispatchPort 主线程接收消息的端口
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer)
            , &livePort, 0, &voucherState, NULL)) {
                // 收到消息,跳过下面休眠,goto第8步处理消息
                goto handle_msg;
            }
        }
        // didDispatchPortLastTime 设置为false,以便进行消息处理
        didDispatchPortLastTime = false;
        // 5. 通知 observers RunLoop即将休眠
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) 
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        // RunLoop 休眠
        __CFRunLoopSetSleeping(rl);
        // 6.线程进入休眠, 直到被下面某一个事件唤醒:
        // a. 基于 port 的 Source1 的事件
        // b. Timer 到时间了
        // c. RunLoop 启动时设置的最大超时时间到了
        // d. 被手动唤醒
        do {
            // 从消息缓冲区获取消息
            msg = (mach_msg_header_t *)msg_buffer;
            // 内部调用 mach_msg() 等待接受 waitSet 的消息
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort
            , poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        } while (1);
        // 设置rl不再等待唤醒
        __CFRunLoopSetIgnoreWakeUps(rl);
        // RunLoop 醒来
        __CFRunLoopUnsetSleeping(rl);
        // 7. 已被唤醒,通知observers
       if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) 
       __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        // 8. 处理消息
        handle_msg:;
        // 设置rl不再等待唤醒
        __CFRunLoopSetIgnoreWakeUps(rl);

        // 8.1 如果livePort不存在
        if (MACH_PORT_NULL == livePort) {
            CFRunLoop_WAKEUP_FOR_NOTHING();
        } else if (livePort == rl->_wakeUpPort) {
            // 8.2 如果是唤醒rl的端口,回到第1步
            CFRunLoop_WAKEUP_FOR_WAKEUP();
            ResetEvent(rl->_wakeUpPort);
        }
        // 8.3 处理timer定时器事件
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            // 处理定时器事件
            CFRunLoop_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
        // 8.4. 处理 主线程 dispatch 消息
        // 比如 dispatch_after、dispatch_async、dispatch timer
        else if (livePort == dispatchPort) {
            CFRunLoop_WAKEUP_FOR_DISPATCH();
            __CFRunLoop_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);     
        } else {
            // 8.5. 除上述4点之外的端口
            CFRunLoop_WAKEUP_FOR_SOURCE();
            // 从端口收到的消息事件,为source1事件
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)

        }
        //判断是否需要退出RunLoop
    } while (!stop && !timeout);
    // 通知即将退出RunLoop
    __CFRunLoopDoObservers(CFRunLoopExit);
}

可以看到RunLoop.run()内部其实是一个do while循环,执行流程大致如下:

1. KCFRunLoopEntry,通知observer。
2. kCFRunLoopBeforeTimers,通知observer即将处理timer。
3. kCFRunLoopBeforeSources,通知observer即将处理source。
4. 开始处理source0事件,如UIEvent(和mach port无关)的事件。
5. 如果右source1事件,跳转到handle_msg,跳过休眠,直接处理source1事件。
6. kCFRunLoopBeforeWaiting,通知observer即将进入休眠,并且进入休眠。
7. kCFRunLoopAfterWaiting,通知observer被唤醒。
8. 处理 timer 定时器。
9. 处理 dispatch_async、dispatch_after、dispatch timer唤醒。
10. 处理 source1 事件。
11. 最后判断是否需要退出RunLoop。

而且执行__CFRunLoopRun必须指定model,即RunLoop总是运行在某个特定的CFRunLoopModeRef下,当切换mode时必须退出当前mode,然后重新进入RunLoop,保证不同mode下的SourceObserverTimer互相隔离的。

Mode Name

CFRunLoopMode的主要有3类。

kCFRunLoopDefaultMode

RunLoop.run()的默认mode

UITrackingRunLoopMode

界面控件跟踪mode,用于UIScrollView等触摸滑动,保证界面滑动时不受其他mode影响。

kCFRunLoopCommonModes

占位mode,不是真正的mode,当Timer等等source被标记CommonModes时,底层会先把timer加入到_commonModeItems中,然后遍历_commonModes里的model,把timer同步到model里的source里.
这里以Timer为例,我们看下看源码:

//RunLoop.swift
open func add(_ timer: Timer, forMode mode: RunLoop.Mode) {
    CFRunLoopAddTimer(_cfRunLoop, timer._cfObject, mode._cfStringUniquingKnown)
}

//CFRunLoop.c
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt
, CFStringRef modeName) {
    //此处省略部分代码...
    __CFRunLoopLock(rl);
    if (modeName == kCFRunLoopCommonModes) {
        //不是CommonModes
        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        if (NULL == rl->_commonModeItems) {
            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        }
        //先把item加入到_commonModeItems里
        CFSetAddValue(rl->_commonModeItems, rlt);
        if (NULL != set) {
            CFTypeRef context[2] = {rl, rlt};
            /* add new item to all common-modes */
            //把新的item加入到所有的common-modes里。
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
            CFRelease(set);
        }
    }
    else {
        //不是CommonModes
        //此处省略...
    }
}

与线程的关系

RunLoop的寄生于线程,一个线程只能有唯一对应的RunLoop;但这个根RunLoop里可以嵌套子RunLoop;子线程刚创建时并没有RunLoop,如果你不主动获取,那它一直都不会有。RunLoop的创建是发生在第一次获取时,RunLoop的销毁是发生在线程结束时。可以通过以下方式获取:

NSRunLoop   *RunLoop = [NSRunLoop currentRunLoop];

我们来看下源码:

//全局字典
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFLock_t loopsLock = CFLockInit;

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

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(_CFThreadRef t) {
    if (pthread_equal(t, kNilPthreadT)) {
    t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    // 第一次进入时,初始化全局字典,并先为主线程创建一个 RunLoop。
    if (!__CFRunLoops) {
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault
    , 0, NULL, &kCFTypeDictionaryValueCallBacks);
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
        CFRelease(dict);
    }
    CFRelease(mainLoop);
    }
    CFRunLoopRef newLoop = NULL;
    //从全局字典中获取 pthread_t 对应的 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); }
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
#if _POSIX_THREADS
            // 注册线程销毁回调,当线程销毁时,执行__CFFinalizeRunLoop,销毁对应的 RunLoop
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1)
            , (void (*)(void *))__CFFinalizeRunLoop);
#else
            _CFSetTSD(__CFTSDKeyRunLoopCntr, 0, &__CFFinalizeRunLoop);
#endif
        }
    }
    return loop;
}

不难看出:

  1. 每条线程都有且只有一个与之对应的RunLoop对象。
  2. RunLoop在第一次获取时创建,在线程结束时会被销毁;只能在一个线程的内部获取其 RunLoop(主线程除外)。

应用

通常我们可以用RunLoop来检测卡顿现象,代码如下:

/// 单次卡顿时长,毫秒
private let BCCatonSingleDuration:Int = 50

public class BCCatonMonitor {

    //MARK: - property
    /// 单例对象
    public static let shared:BCCatonMonitor = BCCatonMonitor()
    /// 状态 observer
    private var observer:CFRunLoopObserver?
    /// runloop 的当前活动状态
    private var currentActivity:CFRunLoopActivity?
    /// 锁
    private var lock:DispatchSemaphore?
    /// 检测耗时的队列
    private var queue:DispatchQueue?
    /// 连续卡顿次数
    private var cartonCount:Int = 0

    //MARK: - life cycle

    //MARK: - 开始监控
    /// 开始监控
    public func start() {
        if self.observer != nil {
            // 已经运行了
            return
        }
        var observerContext = CFRunLoopObserverContext(version: 0, info:unsafeBitCast(self, to: UnsafeMutableRawPointer.self), retain: nil, release: nil, copyDescription: nil)
        let tmpObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, CFRunLoopActivity.allActivities.rawValue, true, 0, { (observer, activity, info) in
            BCCatonMonitor.observerCallBack(observer, activity, info)
        }, &observerContext)
        self.observer = tmpObserver
        CFRunLoopAddObserver(CFRunLoopGetMain(), tmpObserver, CFRunLoopMode.commonModes)
        self.lock = DispatchSemaphore(value: 0)
        self.queue = DispatchQueue(label: "com.zhihan.cartonmonitor", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)
        self.queue?.async { [weak self] in
            guard let self_ = self else {
                return
            }
            while (true) {
                // 假定连续5次超时50ms认为卡顿(当然也包含了单次超时250ms)
                //wait,如果信号量>0, 信号量 -1
                let result = self_.lock?.wait(timeout: DispatchTime.now() + .milliseconds(BCCatonSingleDuration))
                if result != DispatchTimeoutResult.success {
                    //kCFRunLoopBeforeSources 之后是处理 source0 和s ource1
                    //kCFRunLoopAfterWaiting 是处理 timer、main queue dispatch(async、timer) 、mach port 等等事件
                    if self_.currentActivity == CFRunLoopActivity.beforeSources || self_.currentActivity == CFRunLoopActivity.afterWaiting {
                        self_.cartonCount = self_.cartonCount + 1
                        if self_.cartonCount < 5 {
                            continue
                        }
                        //连续5次50ms的卡顿
                        #if DEBUG
                        NSLog("[apm] carton")
                        #endif
                    }
                }
                self_.cartonCount = 0
            }
        }
    }
    /// 停止监控
    public func stop() {
        guard let observer_ = self.observer else {
            // 已经停止运行了
            return
        }
        CFRunLoopRemoveObserver(CFRunLoopGetMain(), observer_, CFRunLoopMode.commonModes)
        self.observer = nil
        self.lock = nil
        self.queue = nil
    }

    //MARK: - 活动状态回调
    /// runloop 活动状态变更回调
    static func observerCallBack(_ observer:CFRunLoopObserver?, _ activity:CFRunLoopActivity, _ info:UnsafeMutableRawPointer?) -> Void {
//        NSLog("%d", activity.rawValue)
        let monitor = unsafeBitCast(info, to: BCCatonMonitor.self)
        monitor.currentActivity = activity
        monitor.lock?.signal()
    }
}

参考链接

iOS刨根问底-深入理解RunLoop

Tags: objc
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章