1、介绍RunLoop
什么是RunLoop
//runloop从字面的意思来看就是:跑 - 圈 也就是运行循环。
//以下是runloop的伪代码。。。
int main(int argc, char * argv[]) {
BOOL running = YES;
do {
//执行各种任务,处理事件
}while (running);
return 0;
}
基本作用:
- 保持程序的持续运行,如果没有RunLoop,程序执行完main函数就结束了。
- 处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
- 节省CPU资源,提高程序性能:该做事时做事,该休息时休息......
2、RunLoop对象
- iOS中有2套API来访问和使用RunLoop
Foundation框架中的NSRunLoop;
Core Foundation中的CFRunLoop;
NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API(Core Foundation层面)
3、RunLoop与线程
每条线程都有唯一的一个与之对应的RunLoop对象
主线程中的RunLoop由系统自动创建,子线程中RunLoop可以通过手动创建
RunLoop在线程结束的时候会被销毁
1、获取RunLoop对象Foundation框架中
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
2、Core Foundation框架中
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象
3.注意点:开一个子线程创建runloop,不是通过alloc init方法创建,而是直接通过调用currentRunLoop方法来创建,它本身是一个懒加载的。
4.在子线程中,如果不主动获取Runloop的话,那么子线程内部是不会创建Runloop的。可以下载CFRunloopRef的源码,搜索_CFRunloopGet0,查看代码。
5.Runloop对象是利用字典来进行存储,而且key是对应的线程Value为该线程对应的Runloop。
4、RunLoop的结构
RunLoop相关联的类有五个
- CFRunLoopRef
- CFRunLoopSourceRef
- CFRunLoopObserverRef
- CFRunLoopTimerRef
- CFRunLoopModeRef
5、RunLoop相关类的理解
1.从上图可以看出
- 一个RunLoop 可以有多个Mode(模式),每个模式又包含若干个 Source/Observer/Timer
- RunLoop 一次只能运行一种Mode,切换Mode需要退出当前Mode
2.CFRunLoopModeRef 一共有五种模式
- kCFRunLoopDefaultMode 默认模式,通常主线程在这个模式下运行
- kCFRunLoopCommonModes 占位符(表示)
- UITrackingRunLoopMode 界面跟踪Mode,用于追踪Scrollview的触摸滑动
- UIInitializationRunLoopMode:刚启动App时进入的第一个Mode,启动后不在使用。
- GSEventReceiveRunLoop:内部Mode,接收系事件。
3.CFRunLoopSourceRef(函数调用栈)
- Source0:非基于Port(处理事件)
- Source1:基于Port (接收分发系统事件)
4.CFRunLoopTimerRef:基于时间的触发器(和NSTimer差不多)
5.CFRunLoopObserverRef:监听RunLoop状态的观察者
CFRunLoopObserverRef是RunLoop的观察者,可以通过CFRunLoopObserverRef来监听RunLoop状态的改变
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 状态值:1,表示进入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 状态值:2,表示即将处理NSTimer
kCFRunLoopBeforeSources = (1UL << 2), // 状态值:4,表示即将处理Sources
kCFRunLoopBeforeWaiting = (1UL << 5), // 状态值:32,表示即将休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 状态值:64,表示从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 状态值:128,表示退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU // 表示监听上面所有的状态
};
};
- 如何监听RunLoop的状态:
- 1.创建CFRunLoopObserverRef
// 第一个参数用于分配该observer对象的内存
// 第二个参数用以设置该observer所要关注的的事件,详见回调函数myRunLoopObserver中注释
// 第三个参数用于标识该observer是在第一次进入run loop时执行还是每次进入run loop处理时均执行
// 第四个参数用于设置该observer的优先级,一般为0
// 第五个参数用于设置该observer的回调函数
// 第六个参数observer的运行状态
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);
});
- 2.将观察者CFRunLoopObserverRef添加到RunLoop上面
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
- 3.观察者CFRunLoopObserverRef要手动释放
CFRelease(observer);````
6、RunLoop 处理逻辑
![RunLoop 处理逻辑](http://upload-images.jianshu.io/upload_images/785453-846c6532a9e0b7ba.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
#####6、RunLoop 使用
1.图片刷新(假如界面要刷新N多图片(渲染),此时用户拖拽UI控件就会出现卡的效果,我们可以通过RunLoop实现,只在RunLoop默认Mode下下载,也就是拖拽Mode下不刷新图片)
- (void)viewDidLoad {
[super viewDidLoad];
// 只在NSDefaultRunLoopMode下执行(刷新图片)
[self.myImageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"0"] afterDelay:2.0 inModes:@[NSDefaultRunLoopMode]];
}
2.在RunLoop中创建定时器:
> - 1、第一种方式
- 定时器NSTimer的创建方式一:timerWithTimeInterval:
- 创建timer
- 将timer 添加到RunLoop中
- 第二种方式
NSTimer 和 scheduledTimerWithTimeInterval,这个不用加在runloop中,默认Mode:NSDefaultRunLoopMode
objc
// 创建一个定时器
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// NSDefaultRunLoopMode:NSTimer只有在默认模式下(NSDefaultRunLoopMode)工作,切换到其他模式不再工作,比如拖拽了界面上的某个控件(会切换成UITrackingRunLoopMode)
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
objc
// 创建一个定时器
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 拖拽UI界面时出发定时器,在默认模式(NSDefaultRunLoopMode)下不工作
[[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
objc
// 创建一个定时器
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// NSRunLoopCommonModes仅仅是标记NSTimer在两种模式(UITrackingRunLoopMode/NSDefaultRunLoopMode)下都能运行,但一个RunLoop中同一时间内只能运行一种模式
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
objc
// 默认已经添加到主线程中RunLoop(mainRunLoop)中(Mode:NSDefaultRunLoopMode)
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
3.保证一个线程永远不死
import "ViewController.h"
/*
思路:为了保证线程不死,我们考虑在子线程中加入RunLoop,
但是由于RunLoop中没有没有源,就会自动退出RunLoop,
所以我们要为子线程添加一个RunLoop,
并且为这个RunLoop添加源(保证RunLoop不退出)
*/
@interface ViewController ()
/** 线程对象 */
@property (nonatomic, strong)NSThread *thread;
@end
@implementation ViewController
-
(void)viewDidLoad {
[super viewDidLoad];// 创建子线程
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];//启动子线程
[self.thread start];
}
-
(void)run {
NSLog(@"run--%@", [NSThread currentThread]); // 子线程
// 给子线程添加一个RunLoop,并且加入源
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
// 启动RunLoop
[[NSRunLoop currentRunLoop] run];
NSLog(@"------------"); // RunLoop启动,这句没有执行的机会
} (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 在子线程中调用test方法,如果子线程还在就能够调用成功
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];
}(void)test {
NSLog(@"test--%@", [NSThread currentThread]); // 子线程
}
@end
#####8、RunLoop的面试题
1.runloop与autoreleasepool的关系
>
- 第一次创建:进入runloop的时候
- 最后一次释放:runloop退出的时候
- 其它创建和释放:当runloop即将休眠的时候会把之前的自动释放池释放,然后重新创建一个新的释放池
2.如何处理滑动UI过程中,广告轮播图停止轮询问题,使用runloop的哪种模式
> 这个问题其实看在"RunLoop中创建定时器"就可以了
- kCFRunLoopDefaultMode
3.runloop有几种模式,runloop接收几种输入源
>
###### runloop有几种模式
- kCFRunLoopDefaultMode 默认模式,通常主线程在这个模式下运行
- kCFRunLoopCommonModes 占位符(表示)
- UITrackingRunLoopMode 界面跟踪Mode,用于追踪Scrollview的触摸滑动
- UIInitializationRunLoopMode:刚启动App时进入的第一个Mode,启动后不在使用。
- GSEventReceiveRunLoop:内部Mode,接收系事件。
> ######runloop接收几种输入源
- Source0:非基于Port(处理事件)
- Source1:基于Port (接收分发系统事件)
#####9、参考资料
[ CFRunLoopRef](http://opensource.apple.com/source/CF/CF-1151.16/)
[官方文档](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html)
[sunnyxxx线下视频](http://pan.baidu.com/s/1o8GRQ9W)
密码: jwnp