本文为L_Ares个人写作,以任何形式转载请表明原文出处。
准备 : RunLoop苹果官方文档
还有 : CFRunLoopRef源码。
既然前一节对线程和进程有了一个最基础的了解了,那么也就可以尝试着来看一下这个几乎所有的要长期跑起来的App
都要玩的路子了,无论你是windows
、安卓
、iOS
,我认为都肯定有这个机制,只不过我只对iOS
的RunLoop
机制有浅显的了解。
为什么说我认为它们都有这个RunLoop
机制,就像前一节的线程中说的,线程是拿来执行任务的,它的生命周期从创建
--->就绪
--->运行
--->阻塞
--->死亡
,也可以看到了,它最后挂了,无论它是怎么挂的,是正常挂了,还是被你手动挂了都不重要,重点就是它挂了,它挂了也不要紧,要紧的是如果一个App
极其的简单,这个App
的进程里面就一个主线程
,其他的什么线程都没有了,那么这个线程挂了,也就代表你的进程没有线程了,这是不应该的呀。你不可能让一个App
执行了一次任务就没了,那么就需要把这个App
保住,这就是RunLoop
存在的意义之一。
一、RunLoop概念基础
1. 什么是RunLoop
通过RunLoop苹果官方文档可以得到官方的基础概念 :
RunLoop概念
RunLoop
是与线程相关的,程序最基本的基础结构之一。RunLoop
是一个处理任务和调度任务的事态循环。
RunLoop思想
- 线程
有任务
的时候进入活跃状态。- 线程
没任务
的时候进入休眠状态。
RunLoop特性
我就不说官方的原话了,总结起来就4点 :
RunLoop
和线程是绑定在一起的,每条线程都有与之一一对应的一个RunLoop
对象。
RunLoop
对象你不需要创建,这是系统提供给你的,直接获取就行。主线程的
RunLoop
对象在程序启动的时候由程序框架自动run
起来,不用我们管。子线程需要我们自己从系统获取对应的
RunLoop
对象,手动让他run
起来。
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 这里知道了CFRunLoopModeRef
是CFRunLoopMode
指针的重定义
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的官方解释
RunLoopModes
可以理解为一个集合。包含了所有要观察的事件源
和观察者
。每次运行
RunLoop
的时候都需要显式或者隐式的指定它要以哪一种mode
运行。
RunLoop
每次只能以一种mode
运行,如果想要切换mode
,只能退出RunLoop
,重新指定另外一种mode
再运行。当设置过要运行的
mode
方式后,RunLoop
会自动过滤掉和其他mode
相关的事件源,只观察和当前mode
相关的事件源,也只给当前mode
的观察者发送通知。大多数时候,
RunLoop
都以系统默认的mode
模式运行,也就是NSDefaultRunLoopMode
。
在NSRunLoop
使用的时候,你会发现NSRunLoop
只有两个公开的mode
。
NSDefaultRunLoopMode
和 NSRunLoopCommonModes
,但是在官方文档中,告知了我们在Cocoa
环境下,一共有5个mode
:
NSDefaultRunLoopMode : 默认模式,通常情况下主线程就是在这个模式下运行的。在
Core Foundation
框架中是kCFRunLoopDefaultMode
。NSConnectionReplyMode :
Cocoa
用这个模式和NSConnection
结合起来使用,用来监测回应。官方建议自己尽量不要使用这个模式。NSModalPanelRunLoopMode :
Cocoa
使用这个模式在Model Panel情况下去区分事件。NSEventTrackingRunLoopMode :
Cocoa
使用这个模式监察来自用户交互的事件。NSRunLoopCommonModes : 这是一种伪模式,这是一组
RunLoopModes
集合,不是单一的mode
。在iOS
系统下,这个模式包含了NSDefaultRunLoopMode
、NSTaskDeathCheckMode
和UITrackingRunLoopMode
。我们可以通过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;
这里可以看到上面说的source
、observers
、timers
这些集合或者数组类型里面存放的都是什么类型的元素。
1. CFRunLoopRef
指向的是__CFRunLoop
结构体。是RunLoop
对象的本质。上面说过了,就不多说了。
2. CFRunLoopSourceRef
指向的是__CFRunLoopSource
联合体。官方文档。
先翻译一下官方说的,再看结构。
- 一个
CFRunLoopSourceRef
对象是一个能被放入RunLoop
的输入源
的抽象对象。输入源
通常生成异步事件。例如:到达网络端口的消息,用户执行的操作。
这就非常明显了,能生成异步事件,证明
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
里面的source0
和source1
集合存放的都是这种联合体。
source0
集合存放version0
变量。
source1
集合存放version1
变量。
接着进入看CFRunLoopSourceContext
和CFRunLoopSourceContext1
:
//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
的结构图。