本文为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起来。
图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 这里知道了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的结构图。
RunLoop结构.png
RunLoop结构图.png











网友评论