一、什么是Runloop
-
顾名思义, Runloop就是运行循环, 在程序运行过程中循环做一些事情
-
应用范畴
- 定时器(Timer)、PerformSelector
- GCD Async Main Queue
- 事件响应、手势识别、界面刷新
- 网络请求
- AutoreleasePool
-
一个OC程序,
main
函数是这样的
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
复制代码
- 程序打开后, 会一直运行
- 如果没有
Runloop
,main
函数返回0
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return 0;
}
}
复制代码
- 此时程序打开后, 会直接关闭, 无法一直运行
- 所以可以写出
Runloop
的伪代码, 如下图所示
do-while
是一个死循环, 当没有任何消息发生的时候, 程序处于睡眠状态等待消息发生- 当消息产生后, 就会处理消息, 接着继续睡眠等待, 程序并不会马上退出,而是保持运行状态
RunLoop的基本作用
保持程序的持续运行
处理App中的各种事件(比如触摸事件、定时器事件等)
节省CPU资源,提高程序性能:该做事时做事,该休息时休息
......
复制代码
二、Runloop对象
- 在iOS中, 有两套API来访问和使用Runloop
- Foundation: NSRunLoop
- Core Foundation: CFRunLoopRef
- NSRunLoop和CFRunLoopRef都代表着RunLoop对象
- NSRunLoop是基于CFRunLoopRef的一层OC封装
- CFRunLoopRef是开源的 opensource.apple.com/tarballs/CF…
三、RunLoop与线程
- 每条线程都有唯一的一个与之对应的RunLoop对象
- RunLoop保存在一个全局的Dictionary里, 线程作为key, RunLoop对象做为Value
- 线程刚创建时并没有RunLoop对象, RunLoop会在第一次获取它时创建
- RunLoop会在线程结束时销毁
- 主线程的RunLoop已经自动获取(创建), 子线程默认没有开启RunLoop
四、获取RunLoop对象
- 可以通过
Foundation
和Core Foundation
来获取RunLoop
1、Foundation
- 获取当前线程的RunLoop
[NSRunLoop currentRunLoop];
复制代码
- 获取主线程的RunLoop
[NSRunLoop mainRunLoop];
复制代码
2、Core Foundation
- 获取当前线程的RunLoop
CFRunLoopGetCurrent()
复制代码
- 获取主线程的RunLoop
CFRunLoopGetMain()
复制代码
五、RunLoop相关的类
- RunLoop相关的类有五个
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
复制代码
CFRunLoopRef
实际类型是__CFRunLoop
- 主要成员结构有下面几个
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
};
复制代码
-
_modes
中存放的是CFRunLoopModeRef
类型数据, 其中就有_currentMode
, 只不过_currentMode
是当前使用的mode
-
CFRunLoopModeRef
的结构如下
- 主要成员变量有下面几个
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
};
复制代码
- 其中的
_sources0
和_sources1
是CFRunLoopSourceRef
类型数据 - 其中的
_observers
是CFRunLoopObserverRef
类型数据 - 其中的
_timers
是CFRunLoopTimerRef
类型数据
六、CFRunLoopModeRef
CFRunLoopModeRef
代表RunLoop
的运行模式- 一个
RunLoop
包含若干个Mode
,每个Mode
又包含若干个Source0/Source1/Timer/Observer
RunLoop
启动时只能选择其中一个Mode
,作为currentMode
- 如果需要切换
Mode
,只能退出当前Loop
,再重新选择一个Mode
进入- 不同组的
Source0/Source1/Timer/Observer
能分隔开来,互不影响
- 不同组的
- 如果
Mode
里没有任何Source0/Source1/Timer/Observer
,RunLoop
会立马退出
常见的两种CFRunLoopModeRef
kCFRunLoopDefaultMode
(NSDefaultRunLoopMode
):App
的默认Mode
,通常主线程是在这个Mode
下运行UITrackingRunLoopMode
: 界面跟踪Mode
,用于ScrollView
追踪触摸滑动,保证界面滑动时不受其他Mode
影响
七、RunLoop的运行逻辑
- Source0
- 触摸事件处理
- performSelector:onThread:
- Source1
- 基于Port的线程间通信
- 系统事件捕捉
- Timers
- NSTimer
- performSelector:withObject:afterDelay:
- Observers
- 用于监听RunLoop的状态
- UI刷新(BeforeWaiting)
- Autorelease pool(BeforeWaiting)
- 可以打印, 当手指点击屏幕时函数的调用栈
- 可以看到屏幕触摸事件是
Source0
处理的
八、监听RunLoop状态
- 我们可以通过给
RunLoop
添加Observer
的方式监听RunLoop
的状态, 使用下面这个函数
/**
给RunLoop添加Observer
@param rl 目标RunLoop
@param observer 需要添加的Observer
@param mode 监听状态
*/
void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);
复制代码
- 可以通过下面的函数创建
Observer
/**
创建Observer
@param allocator 分配器
@param activities 需要监听的状态
@param repeats 是否重复监听
@param order 顺序
@param callout 回调函数
@param context 附加对象
@return 创建好的Observer
*/
CF_EXPORT CFRunLoopObserverRef CFRunLoopObserverCreate(CFAllocatorRef allocator, CFOptionFlags activities, Boolean repeats, CFIndex order, CFRunLoopObserverCallBack callout, CFRunLoopObserverContext *context);
复制代码
RunLoop
的状态有下面几种
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
复制代码
1、监听点击事件
- 创建一个
Observer
, 并将其添加到Runloop
中, 监听屏幕点击事件
- 运行程序就可以看到正在监听
RunLoop
的状态, 当最后没有事件时,Runloop
进入睡眠状态
- 清空打印, 点击屏幕, 有如下结果, 可以看到先进入
sources
状态, 然后执行点击事件
2、监听滚动事件
-
监听
UITextView
的滚动, 查看RunLoop
的currentMode
-
创建
Observer
的另一种方式, 使用block
监听RunLoop
状态
- 在
view
上添加一个UITextView
- 运行程序, 滚动
UITextView
, 有如下打印
- 可以看到滚动之前, 先退出
kCFRunLoopDefaultMode
, 进入UITrackingRunLoopMode
- 等滚动结束, 会先退出
UITrackingRunLoopMode
, 进入kCFRunLoopDefaultMode