一、什么是RunLoop
基本作用:
保持程序的持续运行;
处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
节省CPU资源,提高程序性能:该做事时候做事,该休息时候休息。
main函数中的RunLoop:
UIApplicationMain函数内部就启动了一个RunLoop;
所以UIApplicationMain函数一直没有返回,保持了程序的持续运行;
这个默认启动的RunLoop是跟主线程相关联的。
RunLoop对象
-
iOS中有2套API来访问和使用RunLoop
- Foundation中:
NSRunLoop
。 - Core Foundation中:CFRunLoopRef。
- Foundation中:
NSRunLoop
和CFRunLoopRef都代表着RunLoop对象;NSRunLoop
是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API(Core Foundation层面)RunLoop与线程:
每条线程都有唯一的一个与之对应的RunLoop对象;
主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建;
RunLoop在第一次获取时创建,在线程结束时销毁。
获取RunLoop对象:
Foundation:
[NSRunLoop currentRunLoop] //获得当前线程的NSRunLoop对象
[NSRunLoop mainRunLoop] //获得主线程的NSRunLoop对象
- Core Foundation
CFRunLoopGetCurrent() //获得当前线程的RunLoop对象
CFRunLoopGetMain() //获得主线程的RunLoop对象
二、RunLoop相关类:
Core Foundation中关于RunLoop的5个类:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
-
CFRunLoopObserverRef
CFRunLoopModeRef:
CFRunLoopModeRef代表RunLoop的运行模式;
一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer;
每次RunLoop启动时,只能指定其中一个Mode,这个Mode被称作CurrentMode;
如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入。
这样做主要是为了分割开不同组的Source/Timer/Observer,让其互不影响。
-
系统默认注册了5个Mode:
-
kCFRunLoopDefaultMode
:App默认的Mode,通常主线程是在这个Mode下运行; -
UITrackingRunLoopMode
:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响 - UIInitializationRunLoopMode:在刚启动App时进入的第一个Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到
-
kCFRunLoopCommonModes
:这是一个占位用的Mode,不是一种真正的Mode
-
CFRunLoopTimerRef:
CFRunLoopTimerRef是基于时间的触发器;
基本上说就是NSTimer,它受RunLoop的Mode影响;
GCD的定时器不受RunLoop的Mode影响。
//调用scheduledTimer返回的定时器,已经自动被添加到当前的runloop中,而且模式为NSDefaultRunLoopMode,一旦RunLoop进入其他模式,这个定时器就不会工作。可以通过返回值进行模式修改
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//以下两行代码和上一句代码效果相同,当发生拖拽行为时,就停止调用run方法
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//只有在拖拽情况下才调用run方法,定时器只在UITrackingRunLoopMode模式下工作
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//定时器跑在标记为CommonModes的模式下,CommonModes包含:UITrackingRunLoopMode、kCFRunLoopDefaultMode
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
CFRunLoopSourceRef:一般都由系统确定,了解
CFRunLoopSourceRef是事件源(输入源)
-
按照官方文档,Source的分类
- Port-Based Sources
- Custom Input Source
- Cocoa Perform Selector Sources
-
按照函数调用栈,Source的分类:
- Source0:非基于Port的
- Source1:基于Port的,通过内核和其他线程通信,接受、分发系统事件
CFRunLoopObserverRef:
CFRunLoopObserverRef是观察者,能够监听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
};
//创建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"observer:监听到RunLoop状态发生改变-----%lu",activity);
});
//添加观察者:监听RunLoop的状态
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode
//释放observer
CFRelease(observer);
- CF的内存管理(Core Foundation)
- 凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release操作,比如:CFStringCreate、CFURLCopy等;
- release函数:CFRelease(对象)。
三、RunLoop处理逻辑
-
RunLoop处理逻辑官方版
-
首先判断Mode是否为空,如果不为空就按照下图的逻辑执行:
四、RunLoop应用
NSTimer
ImageView显示
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"place"] afterDelay:2.0 inModes:@[NSRunLoopCommonModes]];
PerformSelector
常驻线程
在线程里面添加runloop(保证线程不死thread->runloop->mode->source(performSelector))
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
- 在子线程保留常驻线程定时做一些操作
- 在主线程创建timer,会自动将timer添加主runloop;
- 方法一:
-(void)viewDidLoad {
[super viewDidLoad];
_thread = [[NSThread alloc] initWithTarget:self selector:@selector(excute) object:nil];
[_thread start];
}
-(void)excute
{
//首先创建一个timer,然后将timer添加到runloop,再启动runloop
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}
- 方法二:通过调用scheduledTimerWithTimeInterval方法,会自动将timer添加到当前runloop中,然后再启动runloop即可:
-(void)viewDidLoad {
[super viewDidLoad];
_thread = [[NSThread alloc] initWithTarget:self selector:@selector(excute) object:nil];
[_thread start];
}
-(void)excute
{
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
- 自动释放池
- 将一些对象放到释放池,等释放池清理时会让池子里面所有的对象调一下release方法;
- 在beforeWait(休眠)之前释放池子。
RunLoop常见问题
什么是RunLoop?
从字面意思看:运行循环、跑圈;
其实它内部就是do-while循环,在这个循环内部不断地处理各种任务(比如Source、Timer、Observer);
一个线程对应一个RunLoop,主线程的RunLoop默认已经启动,子线程的RunLoop得手动启动(调用run方法);
RunLoop只能选择一个Mode启动,如果当前Mode中没有任何Source、Timer、Observer,那么就直接退出RunLoop。
你在开发过程中怎么使用RunLoop?什么应用场景?
-
开启一个常驻线程(让一个子线程不进入消亡状态,等待其他线程发来消息,处理其他时间):
- 在子线程中开启一个定时器;
- 在子线程中进行一些长期监控。
可以控制定时器在特定模式下执行;
可以让某些事件(行为、任务)在特定模式下执行;
可以添加Observer监听RunLoop的状态,比如监听点击事件的处理(在所有点击事件之前做一些事情);
还可以自定义源。
自动释放池是什么?
在RunLoop睡眠之前释放(kCFRunLoopBeforeWaiting)