第二十六节—RunLoop(一)

本文为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起来。

第二十六节—RunLoop(一)_第1张图片
图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(一)_第2张图片
RunLoop结构.png
第二十六节—RunLoop(一)_第3张图片
RunLoop结构图.png

你可能感兴趣的:(第二十六节—RunLoop(一))