文章目录
RunLoop是什么
如果没有RunLoop,那么该线程在执行完自己的任务时就会退出;而我们在使用APP时,通常不会只执行完一个任务就需要其退出,那么我们就需要一种机制,在我们没有任务时依然可以保证线程不退出,随时可以处理事件,这就需要用到RunLoop了。
RunLoop,顾名思义:运行循环,在其内部就是一个大的do-while循环,在这个循环里执行了各种操作,保证线程不退出,程序持续运行。其存在的目的就是:在保证线程不退出的前提下,当线程有任务时执行任务,在线程没有任务的时候就让其睡眠,节省CPU资源,提高程序性能。
NSRunLoop和CFRunLoop
苹果官方源码
我们平时所说的RunLoop有两种,一种是NSRunLoop,一种是CFRunLoop
将下列代码写在主线程中,即主RunLoop和当前的RunLoop是同一个
NSLog(@"%p %p", [NSRunLoop mainRunLoop], [NSRunLoop currentRunLoop]);
NSLog(@"%p %p", CFRunLoopGetMain(), CFRunLoopGetCurrent());

打印结果显示NSRunLoop和CFRunLoop地址不一样,再打印NSRunLoop的信息看看
NSLog(@"%@", [NSRunLoop mainRunLoop]);
NSLog(@"%p %p", CFRunLoopGetMain(), CFRunLoopGetCurrent());

根据箭头所指可以得知,NSRunLoop只是比CFRunLoop多了一层简单的OC封装,底层还是CFRunLoop,CFRunLoop本质是一个结构体,而NSRunLoop是一个NSObject对象。且NSRunLoop存在于Foundation框架中,CFRunLoop是存在于CoreFoundation框架中的。NSRunLoop不是线程安全的,CFRunLoop时候线程安全的。那我们现在看看CFRunLoop是如何实现的
RunLoop内部结构
在分析源码之前,我们先大致了解一下RunLoop的内部结构。CoreFoundation里面关于RunLoop有五个类:
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
他们之间的关系为:(图来源于深入理解RunLoop)

即一个RunLoop里有许多个Mode,一个Mode里有一个Source的set,一个Observer的array和一个Timer的array。这里要注意,虽然一个RunLoop里存在许多Mode,但是每次只能使用一个Mode,需要更换时就退出当前Mode,换下一个Mode
RunLoop基本结构体源码
__CFRunLoop结构体
我们先看看__CFRunLoop结构体是怎么组成的
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */ // 访问mode集合时用到的锁
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp // 手动唤醒runloop的端口。初始化runloop时设置,仅用于CFRunLoopWakeUp,CFRunLoopWakeUp函数会向_wakeUpPort发送一条消息
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop 初始化runloop时设置,仅用于CFRunLoopWakeUp,CFRunLoopWakeUp函数会向_wakeUpPort发送一条消息
pthread_t _pthread; // 与该RunLoop关联的线程
uint32_t _winthread;
CFMutableSetRef _commonModes; // 存储的是字符串,记录所有标记为common的mode
CFMutableSetRef _commonModeItems; // 存储所有commonMode的item(source、timer、observer)
CFRunLoopModeRef _currentMode; // 当前运行的mode
CFMutableSetRef _modes; // 该RunLoop中所有的mode
struct _block_item *_blocks_head; // 链表头指针,该链表保存了所有需要被runloop执行的block。外部通过调用CFRunLoopPerformBlock函数来向链表中添加一个block节点。runloop会在CFRunLoopDoBlock时遍历该链表,逐一执行block
struct _block_item *_blocks_tail; // 链表尾指针,之所以有尾指针,是为了降低增加block时的时间复杂度
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
从__CFRunLoop结构体可以得知:
- RunLoop和线程是一一对应的,一个RunLoop包含一个线程,当该线程结束时RunLoop自动销毁
- 一个RunLoop只有一个_currentMode,即当前运行的mode
- _commonModes 表示NSRunLoopCommonModes这个模式下保存的Mode,我们也可以将自定义的Mode添加到这个set里面
- _commonModeItem 表示添加到NSRunLoopCommonModes里面的Source/Timer/Observer等
- _commonModes里面保存的是被标记为common的mode。这种标记为common的mode有种特性,那就是当 RunLoop 的内容发生变化时,RunLoop 都会自动将 commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。可以这样理解,RunLoop中的_commonModeItems由被标记为common的mode下的各个item(source、observe、timer)组成
__CFRunLoopMode结构体
上文谈到了许多次mode,现在看看mode是怎么组成的:
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name; // mode名称
Boolean _stopped; // mode是否被终止
char _padding[3];
// 几种事件,下面这四个字段,在苹果官方文档里面称为Item
// RunLoop中有个commomitems字段,里面就是保存的下面这些内容
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap; //字典 key是mach_port_t,value是CFRunLoopSourceRef
__CFPortSet _portSet; //保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#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 */
};
需要注意:
- 一个CFRunLoopMode对象中有一个name,许多source0,许多source1,许多Timer,许多Observer和许多port
- 我们在下文会提到的kCFRunLoopDefaultMode就是mode的name,当你传入一个新的 mode name 但 RunLoop 内部没有对应 mode 时,RunLoop会自动帮你创建对应的 CFRunLoopModeRef
- 对于一个 RunLoop 来说,其内部的 mode 只能增加不能删除
- source、timer、observer可以在多个mode中注册,但是只有RunLoop当前的currentMode下的source、timer、observer才可以运行
RunLoop中的mode主要分为以下几种:
- KCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
- UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动的时候不受其他Mode影响
- KCFRunLoopCommonMode:这是一个占位用的Mode,作为标记KCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode(也可以理解为是上面两种mode的结合)
- UIInitializationRunLoopMode:在刚启动App的时候进入的第一个Mode,启动完成后不再使用
- CSEventReceiveRunLoopMode:接受系统事件内部Mode,通常用不到
综合以上,我们可以得知,RunLoop里只保存了许多mode,而mode里保存的才是实际执行的任务
接下来我们剖析mode里包含的三种item
__CFRunLoopSource结构体
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits; //用于标记Signaled状态,source0只有在被标记为Signaled状态,才会被处理
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
union {
// version0、version1 是根据对不同事件的处理区分出来的source0、source1
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
可以看出,Source根据要处理的事件不同分为两个版本,version0和version1,我们分别探究两个的结构体:
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, CFStringRef mode); //当source加入到mode触发的回调
void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode); //当source从RunLoop中移除时触发的回调
void (*perform)(void *info); //当source事件被触发时的回调,使用CFRunLoopSourceSignal方式触发
} CFRunLoopSourceContext;
即:
source0 只包含了回调(函数指针),source0是需要手动触发的Source,它并不能主动触发事件,必须要先把它标记为signal状态。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,也就是通过uint32_t _bits来实现的,只有_bits标记Signaled状态才会被处理。然后手动

最低0.47元/天 解锁文章
1955

被折叠的 条评论
为什么被折叠?



