iOS底层探索-RunLoop

548 阅读17分钟

1、概述

  • 不同于程序执行完代码就会自动退出,App 启动后要保持运行状态,当用户使用某些功能,程序要执行相应代码作出反馈;所以我们需要一个机制,让程序不退出并随时处理事件

    void run() {
        do {
            var message = get_next_message();
            process_message(message);
        } while (message != quit);
    }
    
    • 这种模型通常被称作EventLoop;实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用,在有消息到来时立刻被唤醒
  • RunLoop就是在iOS中的 EventLoop,是一个事件处理循环,属于 线程相关 基础架构的一部分;它用来安排工作,并协调接收传入的事件

  • RunLoop 是一个do..while循环,和普通循环的区别,在有工作的时候让线程保持忙碌,没有工作时让线程休眠

  • 具体内容可参考 RunLoop官方文档

1.1、RunLoop作用

  • 保持程序的持续运行
  • 处理 App 中的各种事件(触摸定时器performSelector
  • 节省 CPU 资源,有事件时处理,没事件时休息

1.2、RunLoop Api

  • OSX/iOS 系统中,提供了NSRunLoopCFRunLoopRef两个对象:
    • NSRunLoop 来自 Foundation 框架,是基于CFRunLoopRef的封装,提供了面向对象的API,但是 这些API不是线程安全的

    • CFRunLoopRef 来自CoreFoundation框架,它提供了C函数的API这些API是线程安全的

2、RunLoop循环

  • Runloop 在底层的封装为CFRunloop,是一个do..while循环
    void CFRunLoopRun(void) {    /* DOES CALLOUT */
        int32_t result;
        do {
            // 1.0e10 : 科学计数 1*10^10
            result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
            CHECK_FOR_FORK();
        } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
    }
    
  • RunLoop 不是一个常规的死循环
    • 设置极大值 1.0e10 来表示 永远 运行

    • 其内部流程:CFRunLoopRun --> CFRunLoopRunSpecific --> __CFRunLoopRun --> __CFRunLoopServiceMachPort

    • 调用核心代码mach_msg函数,等待接受mach_port的消息,线程将进入休眠, 直到被下面某一个事件唤

3、与线程的关系

  • 苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:
    // 主运行循环
    CFRunLoopRef mainRunloop = CFRunLoopGetMain();
    // 当前运行循环
    CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();
    

3.1、主运行循环

  • CFRunLoopGetMain函数
    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;
    }
    
    • 调用_CFRunLoopGet0函数,通过主线程查找

3.2、当前运行循环

  • CFRunLoopGetCurrent函数
    CFRunLoopRef CFRunLoopGetCurrent(void) {
        CHECK_FOR_FORK();
        // 优先 TSD 中查找
        CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
        if (rl) return rl;
    
        //通过当前线程查找
        return _CFRunLoopGet0(pthread_self());
    }
    
    • 优先TSD中查找
    • 调用 _CFRunLoopGet0 函数,通过当前线程查找

3.3、_CFRunLoopGet0

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
        // 不存在,默认为主线程
        t = pthread_main_thread_np();
    }
    __CFSpinLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFSpinUnlock(&loopsLock);
        
        // 创建全局字典,标记为kCFAllocatorSystemDefault
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        // 通过主线程 创建主运行循环
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        
        // dict : key value
        //利用dict,进行key-value绑定操作,线程和Runloop是一一对应的
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        
        CFRelease(mainLoop);
        __CFSpinLock(&loopsLock);
    }
    
    //通过其他线程获取Runloop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFSpinUnlock(&loopsLock);
    if (!loop) {
        // 如果没有获取到,创建一个Runloop
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFSpinLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            // 与线程进行key-value绑定
            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线程 一一对应(创建RunLoop时的必要参数是线程
  • RunLoop 对象在第一次获取 RunLoop 时创建,销毁则是在线程结束的时候
  • 主线程的 RunLoop 对象由系统自动创建,而子线程的 RunLoop 对象需要开发者主动创建
  • RunLoop并不保证线程安全
  • 当前线程内不能操作其他线程的 RunLoop 对象

4、RunLoop结构

  • CoreFoundation 里,包含 RunLoop 的五个类:

    • CFRunLoopRef
    • CFRunLoopModeRef
    • CFRunLoopSourceRef
    • CFRunLoopTimerRef
    • CFRunLoopObserverRef
  • 其中 CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装;它们的关系如下: image.png

  • 1 个 RunLoop 包含 n 个 Mode,每个 Mode 又包含 n 个Source / Timer / Observer image-2.png

  • 每次调用 RunLoop 的主函数时,只能 指定其中一个Mode,这个Mode称之为CurrentMode

  • 若要切换 Mode 只能先退出 Loop再重新指定一个 Mode 进入;这样做主要是为了 分隔开不同组的Source / Timer / Observer,让其互不影响

4.1、CFRunLoopRef

  • 创建RunLoop,返回CFRunLoopRef类型的对象
    static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
        CFRunLoopRef loop = NULL;
        CFRunLoopModeRef rlm;
        uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
        loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopTypeID, size, NULL);
        if (NULL == loop) {
            return NULL;
        }
        (void)__CFRunLoopPushPerRunData(loop);
        __CFRunLoopLockInit(&loop->_lock);
        loop->_wakeUpPort = __CFPortAllocate();
        if (CFPORT_NULL == loop->_wakeUpPort) HALT;
        __CFRunLoopSetIgnoreWakeUps(loop);
        loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
        loop->_commonModeItems = NULL;
        loop->_currentMode = NULL;
        loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        loop->_blocks_head = NULL;
        loop->_blocks_tail = NULL;
        loop->_counterpart = NULL;
        loop->_pthread = t;
    #if DEPLOYMENT_TARGET_WINDOWS
        loop->_winthread = GetCurrentThreadId();
    #else
        loop->_winthread = 0;
    #endif
        rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
        if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
        return loop;
    }
    
  • CFRunLoopRef 也是一个对象,本质是__CFRunLoop结构体类型的指针
    typedef struct __CFRunLoop * CFRunLoopRef;
    
__CFRunLoop 结构体
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;
    
    // 4个重要内容
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFTypeRef _counterpart;
};
  • _commonModes_commonModeItems都是集合类型,表示在Runloop中可能会对应多个

  • 这里有个概念叫commonModes

    • 一个Mode可以将自己标记为Common属性,通过ModeName添加到RunLoop的 commonModes
    • 每当RunLoop的内容发生变化时,RunLoop都会自动将 commonModeItems 里的 Source/Observer/Timer 同步到具有 Common 标记的所有Mode里

4.2、CFRunLoopMode

  • 获取Runloop下的所有Modes
    CFRunLoopRef lp     = CFRunLoopGetCurrent();
    CFArrayRef modeArray= CFRunLoopCopyAllModes(lp);
    NSLog(@"modeArray == %@",modeArray);
    
    -------------------------
    //输出以下内容:
    modeArray == (
        UITrackingRunLoopMode,
        GSEventReceiveRunLoopMode,
        kCFRunLoopDefaultMode
    )
    
    • 1个 RunLoop对应多个Modes
  • 创建一个NSTimer,将其加入Runloop并运行
    NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"fire in home -- %@",[[NSRunLoop currentRunLoop] currentMode]);
    }];
    
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    
    • 添加到RunLoop中,必须指定Mode,这证明Timer的运行依赖于RunLoop的Mode
4.2.1、类型
  1. NSDefaultRunLoopMode:默认的Mode,正常情况下都在该Mode下

  2. NSConnectionReplyMode:将此模式与NSConnection对象结合使用来 监视回复

  3. NSModalPanelRunLoopMode:使用这种模式来识别用于 模态面板的事件

  4. NSEventTrackingRunLoopMode:使用该Mode 跟踪来自用户交互事件,例如:UITableView上下滑动

  5. NSRunLoopCommonModes:伪模式,该集合默认包括默认、模态和事件跟踪模式

  • Mode主要是用于指定RunLoop中事件优先级

  • NSRunLoopCommonModes 是伪模式,本质是Mode的集合,包含 NSDefaultRunLoopModeNSEventTrackingRunLoopMode

4.2.2、结构

__CFRunLoopMode的结构体定义:

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;    /* must have the run loop locked before locking this */
    CFStringRef _name;
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    ...
};
4.2.3、作用
  • 主线程的RunLoop里有两个预置的Mode:kCFRunLoopDefaultModeUITrackingRunLoopModeDefaultMode 是App平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态

  • 当创建一个Timer并加到 DefaultMode 时,Timer会得到重复回调,但此时滑动一个TableView时,RunLoop会将mode切换为 TrackingRunLoopMode,这时 Timer就不会被回调,并且也不会影响到滑动操作

  • 如果 Timer 需要在两个Mode中都能得到回调:

    • 方式一:将这个Timer分别加入这两个Mode
    • 方式二:将Timer加入到顶层的RunLoop的kCFRunLoopCommonModes中,commonModeItems会被RunLoop自动更新到所有具有Common属性的Mode里去
4.2.4、API
  • CFRunLoop对外暴露的管理Mode的API:

    CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
    CFRunLoopRunInMode(CFStringRef modeName, ...);
    
  • 只能通过modeName来操作内部的mode;当传入一个新的 modeName,但RunLoop内部没有对应mode时,RunLoop会自动帮你创建对应的CFRunLoopModeRef

  • 对于一个RunLoop来说,其内部的mode只能增加不能删除

4.3、Items

  • Source/Timer/Observer 被统称 item一个 item 可以被同时加入多个mode
  • 一个item被重复加入同一个mode时是不会有效果的
  • 如果一个mode中一个item都没有,则RunLoop会直接退出,不进入循环
4.3.1、Mode管理item的API:
CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
4.3.2、CFRunLoopSourceRef
  • CFRunLoopSourceRef 是事件产生的地方;Source 分为Source0Source1两种:

    • Source0:表示非系统事件,即用户自定义的事件

      • 只包含了一个函数指针,它并不能主动触发事件
      • 使用时,需要先调用CFRunLoopSourceSignal(source),将Source标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop)来唤醒RunLoop,让其处理这个事件
    • Source1:表示系统事件,主要负责底层的通讯,具备唤醒能力

      • 由RunLoop和内核管理,Mach port驱动
      • 包含了一个 mach_port 和一个函数指针,被用于通过内核和其他线程相互发送消息
      • 这种Source能主动唤醒RunLoop的线程
  • __CFRunLoopSource 的定义:

    struct __CFRunLoopSource {
        CFRuntimeBase _base;
        uint32_t _bits;
        pthread_mutex_t _lock;
        CFIndex _order;            /* immutable */
        CFMutableBagRef _runLoops;
        union {
            CFRunLoopSourceContext version0;    /* immutable, except invalidation */
            CFRunLoopSourceContext1 version1;    /* immutable, except invalidation */
        } _context;
    };
    
    • Source0Source1 在联合体中,实际使用的只能为其中的一种
  • 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, CFStringRef mode);
        void	(*cancel)(void *info, CFRunLoopRef rl, CFStringRef 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_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || 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;
    
4.3.3、CFRunLoopTimerRef
  • CFRunLoopTimerRef 是基于时间的触发器,包含一个时间长度和一个函数指针,当其加入到RunLoop时会注册对应的时间点当时间点到时RunLoop会被唤醒以执行那个回调

  • __CFRunLoopTimer的定义:

    struct __CFRunLoopTimer {
        CFRuntimeBase _base;
        uint16_t _bits;
        pthread_mutex_t _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 */
    };
    
4.3.4、CFRunLoopObserverRef
  • CFRunLoopObserverRef 是观察者,每个Observer都包含了一个函数指针,当RunLoop的状态发生变化时,观察者就能通过回调接受到这个变化

  • __CFRunLoopObserver的定义:

    struct __CFRunLoopObserver {
        CFRuntimeBase _base;
        pthread_mutex_t _lock;
        CFRunLoopRef _runLoop;
        CFIndex _rlCount;
        CFOptionFlags _activities;        /* immutable */
        CFIndex _order;            /* immutable */
        CFRunLoopObserverCallBack _callout;    /* immutable */
        CFRunLoopObserverContext _context;    /* immutable, except invalidation */
    };
    
  • 可监听以下几种RunLoop的状态变化:

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

5、事务处理

Runloop可处理以下事务类型:

  • 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__

5.1、Timer的示例

  • 创建CFRunLoopTimer,将其加入到Runloop并执行

    - (void)cfTimerDemo{
    
        CFRunLoopTimerContext context = {
            0,
            ((__bridge void *)self),
            NULL,
            NULL,
            NULL
        };
        CFRunLoopRef rlp = CFRunLoopGetCurrent();
        /**
         参数一:用于分配对象的内存
         参数二:在什么是触发 (距离现在)
         参数三:每隔多少时间触发一次
         参数四:未来参数
         参数五:CFRunLoopObserver的优先级 当在Runloop同一运行阶段中有多个CFRunLoopObserver 正常情况下使用0
         参数六:回调,比如触发事件,我就会来到这里
         参数七:上下文记录信息
         */
        CFRunLoopTimerRef timerRef = CFRunLoopTimerCreate(kCFAllocatorDefault, 0, 1, 0, 0, lRunLoopTimerCallBack, &context);
        CFRunLoopAddTimer(rlp, timerRef, kCFRunLoopDefaultMode);
    }
    
    void lzRunLoopTimerCallBack(CFRunLoopTimerRef timer, void *info){
        NSLog(@"%@---%@",timer,info);
    }
    
  • 进入CFRunLoopAddTimer函数

    void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
        CHECK_FOR_FORK();
        if (__CFRunLoopIsDeallocating(rl)) return;
        if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
        __CFRunLoopLock(rl);
    
        //匹配kCFRunLoopCommonModes,伪模式,集合类型
        if (modeName == kCFRunLoopCommonModes) {
            //获取集合
            CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
    
            //获取RunLoop下的事务
            if (NULL == rl->_commonModeItems) {
                //为空需要重新创建
                rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
            }
    
            //将传入的CFRunLoopTimer参数,加入到Items
            CFSetAddValue(rl->_commonModeItems, rlt);
    
            //集合不为空
            if (NULL != set) {
                CFTypeRef context[2] = {rl, rlt};
                /* add new item to all common-modes */
                //设置回调函数,添加到common-modes中
                CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
                CFRelease(set);
            }
        } else {
    
            //如果是其他类型,通过名字寻址Mode
            CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
            if (NULL != rlm) {
                if (NULL == rlm->_timers) {
                    CFArrayCallBacks cb = kCFTypeArrayCallBacks;
                    cb.equal = NULL;
                    rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
                }
            }
            if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
                __CFRunLoopTimerLock(rlt);
                if (NULL == rlt->_runLoop) {
                    rlt->_runLoop = rl;
                } else if (rl != rlt->_runLoop) {
                    __CFRunLoopTimerUnlock(rlt);
                    __CFRunLoopModeUnlock(rlm);
                    __CFRunLoopUnlock(rl);
                    return;
                }
                //如果匹配,将Runloop加进去,执行依赖于runloop run
                CFSetAddValue(rlt->_rlModes, rlm->_name);
                __CFRunLoopTimerUnlock(rlt);
                __CFRunLoopTimerFireTSRLock();
                __CFRepositionTimerInMode(rlm, rlt, false);
                __CFRunLoopTimerFireTSRUnlock();
                if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
                    // Normally we don't do this on behalf of clients, but for
                    // backwards compatibility due to the change in timer handling...
                    if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
                }
            }
            if (NULL != rlm) {
                __CFRunLoopModeUnlock(rlm);
            }
        }
        __CFRunLoopUnlock(rl);
    }
    
  • CFRunLoopAddTimer 源码中,没有找到执行事务的函数 所以,真正事务的执行,依赖于runloop run

5.2、__CFRunLoopRun

  • 进入 CFRunLoopRun 函数

    void CFRunLoopRun(void) {    /* DOES CALLOUT */
        int32_t result;
        do {
            result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
            CHECK_FOR_FORK();
        } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
    }
    
    • 通过CFRunLoopRunSpecific函数,得到result结果
    • 1.0e10:科学计数法,1 * 10 ^ 10
  • 进入 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;
        //初始化一个result为kCFRunLoopRunFinished
        int32_t result = kCFRunLoopRunFinished;
    
        if (currentMode->_observerMask & kCFRunLoopEntry )
            /// 1. 通知 Observers: RunLoop 即将进入 loop。
            __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
        /// 真正做事情的代码,调用__CFRunLoopRun函数
        result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
        if (currentMode->_observerMask & kCFRunLoopExit )
            /// 10. 通知 Observers: RunLoop 即将退出。
            __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
        rl->_currentMode = previousMode;
        __CFRunLoopUnlock(rl);
        return result;
    }
    
  • 进入 __CFRunLoopRun 函数

    • 每一种类型都有特定的处理函数调用

5.3、__CFRunLoopDoTimer

  • 进入__CFRunLoopDoTimers函数

    static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) {    /* DOES CALLOUT */
        Boolean timerHandled = false;
        CFMutableArrayRef timers = NULL;
        //程序中正在运行的timer可能不止一个
        for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
    
            //遍历timer,从Mode中获取
            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);
            //执行timer
            Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
            timerHandled = timerHandled || did;
        }
        if (timers) CFRelease(timers);
        return timerHandled;
    }
    
  • 进入 __CFRunLoopDoTimer 函数

    • 进行timer的回调

5.4、处理流程

  1. 通过CFRunLoopAddTimer将timer加入到指定Mode中
  2. 事务的执行依赖于runloop run
    • 调用 CFRunLoopRun --> CFRunLoopRunSpecific 函数,在Runloop进入和离开之间,调用 __CFRunLoopRun 函数
  3. __CFRunLoopRun中,包含对 ObserversSource0Source1Timer 的逻辑处理
    • 针对timer的处理,调用__CFRunLoopDoTimers,遍历当前 正在运行的timer
    • 针对单个timer,调用__CFRunLoopDoTimer函数,执行事务的处理
  • RunLoop事务处理的流程图:

6、底层原理

6.1、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);
    
    /// 1.通知 Observers: RunLoop 即将进入 loop。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
    /// 内部函数,进入loop
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    /// 10.通知 Observers: RunLoop 即将退出。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
    return result;
}

6.2、__CFRunLoopRun 伪代码

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    int32_t retVal = 0;
    
    do {  // itmes do
        
        /// 2.通知 Observers: 即将处理timer事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        
        /// 3.通知 Observers: 即将处理Source事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
        
        /// 处理Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        /// 4.处理sources0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        
        /// 处理sources0返回为YES
        if (sourceHandledThisLoop) {
            /// 处理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }

        /// 5.判断有无端口消息(Source1)
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            /// 如果有Source1,跳到第9步,处理消息
            goto handle_msg;
        }
        
        /// 6.通知 Observers: 即将进入休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
        
        /// 7.等待被唤醒
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

        // user callouts now OK again
        __CFRunLoopUnsetSleeping(rl);
        
        /// 8.通知 Observers: 被唤醒,结束休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
    
    /// 9.处理唤醒时收到的消息
    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) {
            /// 进入loop时参数说处理完事件就返回
            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)) {
            /// source/timer/observer一个都没有了
            retVal = kCFRunLoopRunFinished;
        }
        
    /// 如果没超时,mode里没空,loop也没被停止,那继续loop
    } while (0 == retVal);
    
    return retVal;
}

6.3、流程图

7、RunLoop实战应用

7.1、线程保活

  • 在iOS开发中,有些耗时操作会阻塞主线程,导致界面卡顿,此时我们会创建一个子线程,而后把这些耗时的操作放在子线程中处理

  • 一个常规线程,任务执行完后就会销毁,如果 耗时操作经常需要在子线程中执行,频繁创建和销毁线程,会造成资源浪费,这时我们要用一种方式,让该线程长时间存活而不被销毁

  • 线程保活的三种方式:

    • NSTimer
    • 条件锁NSCondition
    • RunLoopPort
7.1.1、NSTimer
@interface ViewController ()

@property (nonatomic, assign) BOOL isStopping;

@property (nonatomic, strong) LZThread *thread;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    _thread = [[LZThread alloc] initWithBlock:^{

        [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            
            NSLog(@"hello");
            
            if (self.isStopping) {
                // 先将对象置为nil
                self.thread = nil;
                // 再退出线程,RunLoop也停止了
                [NSThread exit];
            }
        }];
        
        [[NSRunLoop currentRunLoop] run];
    }];

    self.thread.name = @"LZ";
    [self.thread start];
}

-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    
    //为常驻线程添加耗时任务
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

-(void)test{
    NSLog(@"耗时任务开始");
    sleep(5);
    NSLog(@"耗时任务结束");
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.isStopping = YES;
}

@end
7.1.2 条件锁(NSCondition
@interface ViewController ()

@property (nonatomic, assign) BOOL isStopping;

@property (nonatomic, strong) LZThread *thread;

@property (nonatomic, strong) NSCondition *condition;

@property (nonatomic, strong) void(^blockTask)(void);

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    _thread = [[LZThread alloc] initWithBlock:^{

        @autoreleasepool {
            
            do {
                
                NSLog(@"hello");
                [self.condition lock];
                
                if(self.blockTask){
                    self.blockTask();
                    self.blockTask = nil;
                }
                
                NSLog(@"闲等...");
                [self.condition wait];
                [self.condition unlock];
                
            } while (!self.isStopping);
            
            //将对象置为nil
            self.thread = nil;
        }
    }];

    self.thread.name = @"LZ";
    [self.thread start];
}

-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    
    //为常驻线程添加耗时任务
    self.blockTask = ^(void){
        NSLog(@"耗时任务开始");
        sleep(5);
        NSLog(@"耗时任务结束");
    };
    
    //通知来任务了,唤醒条件锁
    [self.condition signal];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.isStopping = YES;
    [self.condition signal];
}

- (NSCondition *)condition {
    
    if(!_condition){
        _condition = [[NSCondition alloc] init];
    }
    
    return _condition;
}

@end
7.1.3、RunLoop(Port
@interface ViewController ()

@property (nonatomic, assign) BOOL isStopping;

@property (nonatomic, strong) LZThread *thread;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    _thread = [[LZThread alloc] initWithBlock:^{

        NSRunLoop *loop = [NSRunLoop currentRunLoop];
        [loop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];

        while (!self.isStopping) {
            [loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }

        self.thread = nil;
    }];

    self.thread.name = @"LZ";
    [self.thread start];
}

-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];

    //为常驻线程添加耗时任务
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

-(void)test{
    NSLog(@"耗时任务开始");
    sleep(5);
    NSLog(@"耗时任务结束");
}

- (void)exitThread{
    self.isStopping = YES;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self performSelector:@selector(exitThread) onThread:self.thread withObject:nil waitUntilDone:NO];
}

@end
  • 这种方式,需要注意以下几点:
    • 子线程中创建的RunLoop必须添加Timer、Source0、Source1中的其中一项,如果都不存在RunLoop无法运行;但是 主RunLoop 则不同,即使都不存在也会持续运行

    • addPort本质上就是添加了一个 Source1,它能主动唤醒RunLoop的线程,Source1 的存储采用字典,以当前Source1 ProtkeyCFRunLoopSourceRefvalue;而 Source0 直接使用数组存储,它并不能主动触发事件

    • 需要使用runMode:方法运行RunLoop,不能使用 run 方法,后者是永久循环,无法退出

    • 使用runMode:方法,传入的Mode为 NSDefaultRunLoopMode不能传入 NSRunLoopCommonModes,后者会被标记为kCFRunLoopRunFinished,导致RunLoop直接退出

7.2、App回光返照

+ (void)installUncaughtExceptionHandler {
    NSSetUncaughtExceptionHandler(&LZLGExceptionHandlers);
}

- (void)lz_handleException:(NSException *)exception{

    //处理报错信息,可以写入沙盒文件,下次启动时上传服务器
    [self validateAndSaveCriticalApplicationData:exception];
    
    ...
    
    while (!self.dismissed) {
        for (NSString *mode in (__bridge NSArray *)allModes) {
            //快速切换Mode
            CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
        }
    }
    
    CFRelease(allModes);
    NSSetUncaughtExceptionHandler(NULL);
}
  1. 系统提供的NSSetUncaughtExceptionHandler函数,传入LZExceptionHandlers函数地址

  2. 当出现Crash,会 自动触发 LZExceptionHandlers回调函数

  3. 在回调函数中,拿到当前RunLoop,监听所有Mode

  • 这种方式,相当于应用程序自启的Runloop的平行空间,跟着应用程序保活,并具备响应能力,也就是App的回光返照

7.3、卡顿检测

通过 监听主RunLoop的事务变化 进行卡顿检测

- (void)start{
    [self registerObserver];
    [self startMonitor];
}

static void CallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    LZLGBlockMonitor *monitor = (__bridge LZBlockMonitor *)info;
    monitor->activity = activity;
    // 发送信号
    dispatch_semaphore_t semaphore = monitor->_semaphore;
    dispatch_semaphore_signal(semaphore);
}

- (void)registerObserver{
    CFRunLoopObserverContext context = {0, (__bridge void*)self, NULL, NULL};
    //NSIntegerMax : 优先级最小
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                                            kCFRunLoopAllActivities,
                                                            YES,
                                                            NSIntegerMax,
                                                            &CallBack,
                                                            &context);
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
}

- (void)startMonitor{
    // 创建信号
    _semaphore = dispatch_semaphore_create(0);
    
    // 在子线程监控时长
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (YES)
        {
            // 超时时间是 1 秒,没有等到信号量,st 就不等于 0, RunLoop 所有的任务
            long st = dispatch_semaphore_wait(self->_semaphore, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
            if (st != 0)
            {
                if (self->activity == kCFRunLoopBeforeSources || self->activity == kCFRunLoopAfterWaiting)
                {
                    if (++self->_timeoutCount < 2){
                        NSLog(@"timeoutCount==%lu",(unsigned long)self->_timeoutCount);
                        continue;
                    }
                    // 一秒左右的衡量尺度 很大可能性连续来 避免大规模打印!
                    NSLog(@"检测到超过两次连续卡顿");
                }
            }
            self->_timeoutCount = 0;
        }
    });
}
  1. 定义信号量,在全局并发队列中,加入异步函数,创建while死循环,内部让信号量进入休眠状态,定义一秒的超时时间

  2. 监听主RunLoop的所有事务,在回调方法中,对信号量发送释放的通知

  3. 如果信号量时间,检查Observer的状态。如果是处理Sources或处于唤醒状态,证明还在做事情,将超时次数+1

  4. 如果连续两次,视为卡顿

  • 解决方式:可以考虑将部分业务代码,在Observer回调函数中,RunLoop处于kCFRunLoopBeforeWaiting状态时运行。因为RunLoop进入即将休眠状态时,此时没有其他任务,CPU等资源相对空闲。这种方式相当于代码的错峰执行,可以对卡顿有一定程度的优化

总结

RunLoop作用

  • 保持程序的持续运行
  • 处理App中的各种事件(触摸、定时器、performSelector)
  • 节省CPU资源,该做事就做事,该休息就休息\

与线程的关系

  • RunLoop与线程一一对应
  • RunLoop对象在第一次获取RunLoop时创建,销毁则是在线程结束的时候
  • 主线程的RunLoop对象由系统自动创建,而子线程的RunLoop对象需要开发者主动创建
  • RunLoop并不保证线程安全
  • 当前线程内部不能操作其他线程的RunLoop对象

结构

  • 线程与RunLoop一对一
  • 一个RunLoop对应多个Modes
  • 一个Mode对应多个Items
  • Source、Timer、Observer统称Item

Mode

  • Mode主要是用于指定RunLoop中事件优先级
  • NSDefaultRunLoopMode:默认的Mode,正常情况下都在该Mode下
  • NSConnectionReplyMode:将此模式与NSConnection对象结合使用来监视回复
  • NSModalPanelRunLoopMode:使用这种模式来识别用于模态面板的事件
  • NSEventTrackingRunLoopMode:使用该Mode跟踪来自用户交互的事件,例如:UITableView上下滑动
  • NSRunLoopCommonModes:伪模式,该集合默认包括默认、事件跟踪模式

Runloop可处理以下事务(Item)类型

  • 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

Source

  • 可以唤醒RunLoop的一些事件
  • Source0:表示非系统事件,即用户自定义的事件
  • Source1:表示系统事件,主要负责底层的通讯,具备唤醒能力

Timer

  • 常用NSTimer定时器

Observer

  • 用于监听RunLoop的状态变化,并作出一定响应
  • kCFRunLoopEntry:进入
  • kCFRunLoopBeforeTimers:即将处理Timers
  • kCFRunLoopBeforeSources:即将处理Sources
  • kCFRunLoopBeforeWaiting:进入休眠
  • kCFRunLoopAfterWaiting:被唤醒
  • kCFRunLoopExit:退出

事务处理流程

  • 以timer为例,通过CFRunLoopAddTimer将timer加入到指定Mode中
  • 事务的执行依赖于runloop run
  • __CFRunLoopRun中,包含对Observers、Source0、Source1、Timer的逻辑处理

底层原理

  1. 通知Observer即将进入Loop(Observer)
  2. 通知Observer将要处理Timer(Observer)
  3. 通知Observer将要处理Source0(Observer)
  4. 处理Source0(Source0)
  5. 如果有Source1,跳到【第九步】(Source1)
  6. 通知Observer线程即将休眠(Observer)
  7. 休眠,等待唤醒。可通过Source1(port)、Timer、外部手动唤醒
  8. 通过Observer线程刚被唤醒(Observer)
  9. 处理唤醒时收到的消息,之后跳回【第二步】(Timer、Source1) 10.通知Observer即将退出Loop(Observer)

参考链接:www.yuque.com/u12101430/a…