runLoop表面意思是运行循环,程序在运行过程中循环做一些事情
应用范畴:
- 定时器
- GCD
- 事件响应
- 网络请求
- AutoreleasePool
<如果没有runLoop以上基本会失效.每一个线程都有唯一的一个与之对应的runLoop>
runLoop基本作用
- runLoop可以保持程序的持续运行
- 处理app中的各种事件(点击,触摸,定时器等等)
- 节省CPU资源,提高程序性能:做该做的事,该休息时候休息.
runLoop对象 : iOS 中有2套API来访问和使用runLoop
- Foundation : NSRunLoop
- Core Foundation : CFRunLoopRef
<他们可以相互转换, NSRunLoop是基于CFRunLoopRef的一层OC的封装>
<CFRunLoopRef是开源的 >
源码指南
runLoop和线程关系
- 每条线程都有唯一的一个与之对应的runLoop对象
- runLoop保存在一个全局的NSDictionary中,线程作为key,runloop做为value
- 线程刚创建时候并没有runLoop对象,runLoop会在第一次获取它时候创建(后面会看源码分析)
- runLoop会在线程结束时销毁
获取runLoop对象
// Foundation框架下
[NSRunLoop currentRunloop]; // 获取当前线程的runLoop对象
[NSRunLoop mainRunLoop]; // 获取主线程的runLoop对象
// Core Foundation 下
CFRunloopGetCurrent(); // 获取当前线程的runLoop对象
CGRunLoopGetMain(); // 获取主线程的runLoop对象
获取runLoop源码.png
RunLoop相关的类
- CFRunLoopRef
- CFRunLoopModelRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
// 源码
typedef struct __CFRunLoop * CGRunLoopRef;
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes; // 当前模式
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes; // 集合模式, 下面的 __CFRunLoopMode
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
struct __CFRunLoopMode {
CFMutableSetRef _sources0; //
CFMutableSetRef _sources1; //
CFMutableArrayRef _observers; //
CFMutableArrayRef _timers; //
};
runLoop内部结构.png
<👆源码得出结论 : 一个runLoop含有很多modes,modes都是source0(CFRunLoopSourceRef对象),source1,timers(CFRunLoopTimerRef对象),observers(CFRunLoopObserverRef对象) 👇的图可以解释 : >
- CFRunLoopModeRef 代表RunLoop运行模式
- 一个RunLoop包含若干个mode ,每一个Mode又包含若干个 Source0/ Source1/Timer/ Observer
- RunLoop启动时只能选择其中一个Mode , 作为currentMode
- 如果需要切换Mode,只能退出当前RunLoop,再重新选择一个Mode进入
常见的运行模式
- kCFRunLoopDefaultMode: app的默认Mode,通常主线程在这个Mode下运行
- UITrackingRunLoopMode : 界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
- UIInitalizationRunLoopMode : 在刚启动APP是进入的第一个Mode,启动后就不再使用
- GSEventReceiveRunLoopMode : 接收系统事件的内部Mode,通常不用
- kCFRunLoopCommonModes : 这是一个占位用Mode,不是一种真正的Mode
RunLoop运行逻辑
Source0
- 触摸事件
- Perform Selectors
Source1
基于Port的线程间通信
Timers
- 定时器,NStimer
Observer
- 监听器
- 用于监听RunLoop的状态
运行步骤:
- [1] 通知Observer: 进入loop
- [2] 通知Observer: 即将处理timers
- [3] 通知Observer: 即将处理Sources
- [4] 处理Blocks
- [5] 处理Source0(可能再次处理Blocks)
- [6] 如果有Source1,跳到 [8]
- [7] 开始休眠,等待消息唤醒
- [8] 被某个消息唤醒
- [1] 处理timer
- [2] 处理 GCD Async To Main Queue
- [3] 处理Source1
- [9] 处理Blocks
- [10] 根据前面执行结果,决定做什么
- [1] 回到 [2]
- [2] 退出loop
- [11] 通知Observer: 退出Loop
image.png👇是一个touch事件, 控制台输入 bt, 查看调用栈 :
👇源码分析
//
// RunLoop.c
//
// Created by majianjie on 2018/3/28.
// Copyright © 2018年 JJCoder. All rights reserved.
//
#include "RunLoop.h"
// 开始调用 CFRunLoopRun
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
// 通知 Observer 进入runLoop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// runLoop 核心逻辑
__CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知observer 退出runLoop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
int32_t retVal = 0;
do {
// 1. 通知observers 将要处理 timers
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 2. 通知observers 将要处理 sources0
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 3. 通知observers 将要处理 blocks
__CFRunLoopDoBlocks(rl, rlm);
// 4. 通知observers 将要处理 sources0
__CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
// 5. MachPort 监听 如果有source1事件 就跳到 handle_msg
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
// 6. 通知observer即将进入休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 7. 进入休眠 等待其他消息唤醒runLoop
__CFRunLoopSetSleeping(rl);
__CFPortSetInsert(dispatchPort, waitSet);
do {
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
} while (1);
// 8. 醒了
__CFPortSetRemove(dispatchPort, waitSet);
__CFRunLoopSetIgnoreWakeUps(rl);
__CFRunLoopUnsetSleeping(rl);// 不睡觉了
// 9. 通知observer 我醒了
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
// 10. 处理 handle_msg
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
// 11 看看是谁唤醒了runLoop 进行相应的处理
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// do nothing on Mac OS
}else if (/*被timer唤醒的*/) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
// 处理timer
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time()
}
else if (livePort == dispatchPort/*被gcd唤醒的*/) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else {
// 被source1唤醒的
CFRUNLOOP_WAKEUP_FOR_SOURCE();
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
}
// 执行block
__CFRunLoopDoBlocks(rl, rlm);
// 根据前面执行的结果 来决定 做什么? / 怎么做?
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
// 如果上面一轮下面 retVal 仍然等于 0 继续循环 否则 退出当前runLoop循环
// 释放相关资源
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
// 返回了
return retVal;
}
验证 GCD的一些操作 也是通过 RunLoop来处理的 ?
image.png👆调用栈 中有 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE 方法调用,而 源码分析中 有这个函数的处理,是否可以说明 GCD 也是依赖runLoop来处理的呢?
网友评论