美文网首页
第二十六节—RunLoop(一)

第二十六节—RunLoop(一)

作者: L_Ares | 来源:发表于2020-11-15 02:34 被阅读0次

    本文为L_Ares个人写作,以任何形式转载请表明原文出处。

    准备 : RunLoop苹果官方文档
    还有 : CFRunLoopRef源码

    既然前一节对线程和进程有了一个最基础的了解了,那么也就可以尝试着来看一下这个几乎所有的要长期跑起来的App都要玩的路子了,无论你是windows安卓iOS,我认为都肯定有这个机制,只不过我只对iOSRunLoop机制有浅显的了解。

    为什么说我认为它们都有这个RunLoop机制,就像前一节的线程中说的,线程是拿来执行任务的,它的生命周期从创建--->就绪--->运行--->阻塞--->死亡,也可以看到了,它最后挂了,无论它是怎么挂的,是正常挂了,还是被你手动挂了都不重要,重点就是它挂了,它挂了也不要紧,要紧的是如果一个App极其的简单,这个App的进程里面就一个主线程,其他的什么线程都没有了,那么这个线程挂了,也就代表你的进程没有线程了,这是不应该的呀。你不可能让一个App执行了一次任务就没了,那么就需要把这个App保住,这就是RunLoop存在的意义之一。

    一、RunLoop概念基础

    1. 什么是RunLoop

    通过RunLoop苹果官方文档可以得到官方的基础概念 :

    RunLoop概念
    1. RunLoop与线程相关的,程序最基本的基础结构之一。
    2. RunLoop是一个处理任务和调度任务的事态循环
    RunLoop思想
    1. 线程有任务的时候进入活跃状态
    2. 线程没任务的时候进入休眠状态
    RunLoop特性

    我就不说官方的原话了,总结起来就4点 :

    1. RunLoop和线程是绑定在一起的,每条线程都有与之一一对应的一个RunLoop对象。

    2. RunLoop对象你不需要创建,这是系统提供给你的,直接获取就行。

    3. 主线程的RunLoop对象在程序启动的时候由程序框架自动run起来,不用我们管。

    4. 子线程需要我们自己从系统获取对应的RunLoop对象,手动让他run起来。

    图1.1.0.png

    2. 系统提供的RunLoop对象

    • CFRunLoopRef : 这是Core Foundation框架内的,它提供了纯C函数的API,并且这些API都是线程安全的。(这个有开源的源码看)

    • NSRunLoop : 这个就太常见了,这个是基于上面那个CFRunLoopRef进行面向对象的封装,提供的也是面向对象的API,但是这些API不是线程安全的。(这是Foundation框架的,没有源码可以看)。

    获取RunLoop对象的方式 :
    • CFRunLoopRed获取RunLoop对象 :
    // Core Foundation框架
    CFRunLoopGetMain();     // 获取主线程的 RunLoop 对象
    CFRunLoopGetCurrent();  // 获取当前线程的 RunLoop 对象
    
    • NSRunLoop获取RunLoop对象 :
    //Foundation框架
    [NSRunLoop mainRunLoop];     //获取主线程 RunLoop 对象
    [NSRunLoop currentRunLoop];  //获取当前线程的 RunLoop 对象
    

    二、RunLoop的结构

    上面的基础中说过了,iOS提供了两种RunLoop对象,

    • 一个开源的CFRunLoopRef
    • 一个没有开源的NSRunLoop

    没有开源的不好说,但是知道的是NSRunLoop也是基于CFRunLoopRef实现的,那么这里就从CFRunLoopRef的结构来探索RunLoop的结构。

    1. CFRunLoopRef

    VSCode打开准备的源码文件,找到CFRunLoop.c,找CFRunLoopRef

    1.1 这里知道了CFRunLoopRef__CFRunLoop结构体指针的重定义
    typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
    
    1.2 __CFRunLoop结构体
    struct __CFRunLoop {
        CFRuntimeBase _base;
        //访问模式列表的锁
        pthread_mutex_t _lock;          /* locked for accessing mode list */
        //唤醒RunLoop的端口
        __CFPort _wakeUpPort;           // used for CFRunLoopWakeUp 
        Boolean _unused;
        //重置RunLoop的运行次数
        volatile _per_run_data *_perRunData;              // reset for runs of the run loop
        //RunLoop对应的线程
        pthread_t _pthread;
        uint32_t _winthread;
        //一个集合,存储着mode的名字
        CFMutableSetRef _commonModes;
        //被标记了commoMode的item的集合
        CFMutableSetRef _commonModeItems;
        //当前的mode
        CFRunLoopModeRef _currentMode;
        //存储着RunLoop所有的 Mode(CFRunLoopModeRef)模式
        CFMutableSetRef _modes;
        //_block_item链表的表头指针
        struct _block_item *_blocks_head;
        //_block_item链表的表尾指针
        struct _block_item *_blocks_tail;
        //运行的时间
        CFAbsoluteTime _runTime;
        //休眠的时间
        CFAbsoluteTime _sleepTime;
        CFTypeRef _counterpart;
    };
    
    

    所以RunLoop本质是一个结构体。有着pthread_t线程,有着几个和mode相关的集合,有一个currentMode。既然都和mode有关,那么必然要找mode是什么东西。CFRunLoopModeRef,这是什么?

    2. CFRunLoopModeRef

    2.1 这里知道了CFRunLoopModeRefCFRunLoopMode指针的重定义
    typedef struct __CFRunLoopMode *CFRunLoopModeRef;
    
    2.2 看CFRunLoopModeRef是什么结构
    struct __CFRunLoopMode {
        CFRuntimeBase _base;
        //在加锁之前,必须有runloop锁,也就是说runloop被加锁了以后,mode才加锁
        pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
        //mode的名字
        CFStringRef _name;
        //mode是否停止
        Boolean _stopped;
        //一个填充,数组形式,具体填充什么还不知道
        char _padding[3];
        //0号输入源集合
        CFMutableSetRef _sources0;
        //1号输入源集合
        CFMutableSetRef _sources1;
        //观察者数组
        CFMutableArrayRef _observers;
        //定时器数组
        CFMutableArrayRef _timers;
        //不知道是什么,反正是个字典,而且看名字是端口port和源source组成的
        CFMutableDictionaryRef _portToV1SourceMap;
        //端口集合
        __CFPortSet _portSet;
        CFIndex _observerMask;
        //如果用GCD的定时器,宏定义这个是0
    #if USE_DISPATCH_SOURCE_FOR_TIMERS
        //GCD定时器
        dispatch_source_t _timerSource;
        //GCD队列
        dispatch_queue_t _queue;
        //判断是否启动了GCD定时器
        Boolean _timerFired; // set to true by the source when a timer has fired
        //判断GCD定时器是否被装起来
        Boolean _dispatchTimerArmed;
    #endif
        //这个宏定义才是1
    #if USE_MK_TIMER_TOO
        mach_port_t _timerPort;
        Boolean _mkTimerArmed;
    #endif
    #if DEPLOYMENT_TARGET_WINDOWS
        DWORD _msgQMask;
        void (*_msgPump)(void);
    #endif
        uint64_t _timerSoftDeadline; /* TSR */
        uint64_t _timerHardDeadline; /* TSR */
    };
    

    一个CFRunLoopModeRef拥有 :

    • 唯一的name名字。
    • 一个source0集合。
    • 一个source1集合。
    • 一个_observers观察者数组
    • 一个_timers定时器数组
    2.3 RunLoopMode的官方解释
    1. RunLoopModes可以理解为一个集合。包含了所有要观察的事件源观察者

    2. 每次运行RunLoop的时候都需要显式或者隐式的指定它要以哪一种mode运行。

    3. RunLoop每次只能以一种mode运行,如果想要切换mode,只能退出RunLoop,重新指定另外一种mode再运行。

    4. 当设置过要运行的mode方式后,RunLoop会自动过滤掉和其他mode相关的事件源,只观察和当前mode相关的事件源,也只给当前mode的观察者发送通知。

    5. 大多数时候,RunLoop都以系统默认的mode模式运行,也就是NSDefaultRunLoopMode

    NSRunLoop使用的时候,你会发现NSRunLoop只有两个公开的mode
    NSDefaultRunLoopModeNSRunLoopCommonModes,但是在官方文档中,告知了我们在Cocoa环境下,一共有5个mode :

    • NSDefaultRunLoopMode : 默认模式,通常情况下主线程就是在这个模式下运行的。在Core Foundation框架中是kCFRunLoopDefaultMode

    • NSConnectionReplyMode : Cocoa用这个模式和NSConnection结合起来使用,用来监测回应。官方建议自己尽量不要使用这个模式。

    • NSModalPanelRunLoopMode : Cocoa使用这个模式在Model Panel情况下去区分事件。

    • NSEventTrackingRunLoopMode : Cocoa使用这个模式监察来自用户交互的事件。

    • NSRunLoopCommonModes : 这是一种伪模式,这是一组RunLoopModes集合,不是单一的mode。在iOS系统下,这个模式包含了NSDefaultRunLoopModeNSTaskDeathCheckModeUITrackingRunLoopMode。我们可以通过Core Foundation框架下的CFRunLoopAddCommomMode()函数,把自定义的mode放到这里面。但是如果你将input source加入到这个模式下,就意味着input source会关联这个模式集合中的所有模式。

    三、 RunLoop结构有关的类

    CFRunLoop.h文件。

    // CFRunLoop.h
    //重命名 : 
    typedef struct __CFRunLoop * CFRunLoopRef;
    
    typedef struct __CFRunLoopSource * CFRunLoopSourceRef;
    
    typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
    
    typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;
    

    这里可以看到上面说的sourceobserverstimers这些集合或者数组类型里面存放的都是什么类型的元素。

    1. CFRunLoopRef

    指向的是__CFRunLoop结构体。是RunLoop对象的本质。上面说过了,就不多说了。

    2. CFRunLoopSourceRef

    指向的是__CFRunLoopSource联合体。官方文档

    先翻译一下官方说的,再看结构。

    1. 一个CFRunLoopSourceRef对象是一个能被放入RunLoop输入源的抽象对象。
    2. 输入源通常生成异步事件。例如:到达网络端口的消息,用户执行的操作。

    这就非常明显了,能生成异步事件,证明CFRunLoopSourceRef对象是一个事件,或者说是一个任务,是要被执行的。

    看结构 :

    struct __CFRunLoopSource {
        CFRuntimeBase _base;
        //数据
        uint32_t _bits;
        //互斥量
        pthread_mutex_t _lock;
        CFIndex _order;         /* immutable */
        CFMutableBagRef _runLoops;
        //这里的version0和version1也就对应了CFRunLoopMode里面的source0和source1
        union {
            CFRunLoopSourceContext version0;    /* immutable, except invalidation */
            CFRunLoopSourceContext1 version1;   /* immutable, except invalidation */
        } _context;
    };
    

    也就是说CFRunLoopMode里面的source0source1集合存放的都是这种联合体。

    source0集合存放version0变量。
    source1集合存放version1变量。

    接着进入看CFRunLoopSourceContextCFRunLoopSourceContext1 :

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

    version0 : 结构体。并且结构体内的元素全部都是函数指针。根据官方文档的介绍。它只包含函数指针(回调),并且需要手动触发事件。必须要调用CFRunLoopSourceSignal(source);函数,将这个输入源标记为准备触发,然后手动调用CFRunLoopWakeUp(runloop)唤醒RunLoop处理输入源的事件。

    version1 : 结构体。结构体中包含函数指针和一个mach_port_t。根据官方文档的介绍。它是被RunLoop内核共同管理的。主要用于通过内核和其他线程互相发送消息。它是主动唤醒RunLoop的线程的。

    3. CFRunLoopObserverRef

    指向的是__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 */
    };
    

    这是一个观察者。也是CFRunLoopMode中的observers数组中的元素类型。

    看结构,重点的是有一个回调,作为观察者,当RunLoop的状态发生改变的时候,它就可以通过这个回调接收到RunLoop的变化状态。

    看一下这个CFRunLoopObserverCallBack回调的类型是什么 :

    typedef void (*CFRunLoopObserverCallBack)(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
    
    • 参数1 : observer,也即是观察者自己。
    • 参数2 : activity,看名字是RunLoop的活动状态。
    • 参数3 : info,一些传回来的信息。

    看参数2的RunLoop的活动状态CFRunLoopActivity是什么 :

    /* Run Loop Observer Activities */
    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的所有活动
    };
    

    4. CFRunLoopTimerRef

    指向的是__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 */
    };
    

    包含了下次触发的时间、一个时间长度、一个回调函数。也就是说RunLoop会记录一个被注册的时间点,等到时间点到了就会唤醒自己,执行那个回调。

    四、总结

    既然本节介绍的就是RunLoop的基本概念和基本结构,那就总结一个RunLoop的结构图。

    RunLoop结构.png RunLoop结构图.png

    相关文章

      网友评论

          本文标题:第二十六节—RunLoop(一)

          本文链接:https://www.haomeiwen.com/subject/dczibktx.html