第二十六节—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.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的结构图。
第二十六节—RunLoop(一)
文章图片
RunLoop结构.png 第二十六节—RunLoop(一)
文章图片
RunLoop结构图.png

    推荐阅读