iOS-RunLoop
2016年8月2日
RunLoop
有两种类型的RunLoop,一种是Foundation框架的 NSRunLoop;另一种是Core Foundation核心框架的CFRunLoopRef。前者是OC语言封装的,后者是纯C语言底层的。
基本作用:
- 保持程序的持续运行
- 处理App中的各种事件(触摸事件、定时器事件、Selector事件)
- 节省CPU资源,提供程序性能
1 Main函数中的RunLoop
int main(int argc,char *argv[])
{
@autoreleasepool{
return UIApplicationMain(argc,argv,nil,NSStringFromClass([AppDelegate class]))
}
}
UIApplicationMain实际上是没有返回值的,因为在内部启动了一个RunLoop循环。
这个默认启动的RunLoop是跟主线程相关联的。
2 RunLoop与线程
- 每条线程都有一个唯一的RunLoop对象与其对应
- 主线程的RunLoop已经自动创建好了并启动了,子线程的RunLoop需要自行创建并启动
- RunLoop在第一次获取时创建(懒加载的形式),在线程结束时销毁。
- 源代码中,使用一个字典来存储线程和RunLoop的对应关系
3 获取RunLoop对象
(1)Foundation对象
//获取主线程的运行循环
[NSRunLoop mainRunLoop];
//获取当前线程的RunLoop对象(如果没有,就会创建,是懒加载形式)
[NSRunLoop currentRunLoop];
(2)Core Foundation对象
//获取当前线程的运行循环
CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();
//获取主线程的运行循环
CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
(3)互相转换
CFRunLoopRef *cfMainRunLoop = [NSRunLoop mainRunLoop].getCFRunLoop;
4 RunLoop运行模式Mode
一个RunLoop可以包含多个Mode,每个RunLoop启动后,只能执行一个Mode。
系统默认注册了五个运行模式(以Core Foundation为例,Foundation跟其一样,改个NS前缀就行)
- kCFRunLoopDefaultMode(NSDefaultRunLoop).App默认的Mode,通过主线程就在这个mode下执行
- UITrackingRunLoopMode.界面追踪模式,ScrollView等滚动界面时自动注册到这个模式,保证界面滑动不受其他Mode影响。
- UIInitializationRunLoopMode。刚启动时进入的模式,启动后不再使用
- GSEventReceiveRunLoopMode。接收系统事件的内部Mode,通常用不到
- kCFRunLoopCommonModes(NSRunLoopCommonModes).占位的一种标记,不是严格意义上的Mode。如果标记为Common,则该RunLoop既可以运行在DefaultMode,也可以运行在界面追踪模式。
5 NSTimer
NSTimer定时器,有两种创建的方式:
//1 创建计时器,自动加入到当前运行循环,并启动。默认为DefaultMode
NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(doSomething) userInfo:nil repeats:YES];
//2 创建定时器,需要手动加入当前的运行循环
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(doSomething) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
注意:子线程是需要我们手动创建运行循环并启动运行的。如果当前计时器是在子线程中创建,需要获取一下当前运行循环(因为懒加载,会自动创建)。然后并启动。
//当前的线层为子线程
NSLog(@"%@",[NSThread currentThread]);
//创建子线程对应的运行循环--因为是懒加载,如果没有会自动创建
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
//创建定时器1--自动加入循环并启动
//[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(doSomething) userInfo:nil repeats:YES];
//创建定时器2--手动加入循环并启动
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(doSomething) userInfo:nil repeats:YES];
[currentRunLoop addTimer:timer forMode:NSRunLoopCommonModes];
//启动运行循环
[currentRunLoop run];
6 GCD定时器
XCode提供了代码段,输入dispatch_source_timer回车就可以。
有三部分:
- 创建定时器
- 设置定时器
- 设置定时器任务
- 启动定时器
代码示例:
//1 创建GCD定时器
/*
* @param DISPATCH_SOURCE_TYPE_TIMER CFRunLoopSourceRef类型(定时器也是一种source)
* @param 0 暂时不清楚,默认就是0
* @param 0 暂时不清楚,默认就是0
* @param dispatchQueue 定时器任务执行的队列
*/
//---1.1 创建全局队列(任务执行的队列)
dispatch_queue_t dispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//---1.2 创建定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatchQueue);
self.timer = timer;//延长生命周期
//2 设置定时器(启动时间、时间间隔、定时器处理的任务)
/**
*第一个参数:timer,设置哪个定时器
*第二个参数:DISPATCH_TIME_NOW 启动时间
*第三个参数:时间间隔(秒),他会自动转换成系统支持的纳秒
*第三个参数:精确度,0表示绝对精确
*/
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
//3 设置定时器任务
dispatch_source_set_event_handler(timer, ^{
NSLog(@"定时器执行任务");
});
//4 启动定时器(需要注意timer的声明周期,避免提前销毁了,但是任务还没开始执行)
dispatch_resume(timer);
<b style="color:red">
注意:
相比NSTimer,GCD定时器不会受RunLoopMode的影响。精度比NStimer高
</b>
7 CFRunLoopSourceRef
就是一个RunLoop的源,其实timer也是源的一种。除了timer之外,源的分类分两种情况
<b style="color:orange">
之前的分类:
</b>
- Port,基于端口的源
- 自定义源
- perfrom等执行事件
<b style="color:orange">
之后的分类(可以在函数调用栈中看出来--断点调试的时候能够看到):
</b>
- source0,非基于端口的源
- source1,基于端口的源
<b style="color:orange">
给RunLoop添加Source
</b>
//1 Foundation的添加源
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
//2 Core Foundation添加源
CFRunLoopSourceRef source = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, nil);
CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopDefaultMode);
8 CFRunLoopObserverRef
观察者:也就是RunLoop的观察者,能够监听RunLoop的状态改变。
RunLoop的状态有如下几种:
/**
* kCFRunLoopEntry = (1UL << 0), 当RunLoop启动的时候
kCFRunLoopBeforeTimers = (1UL << 1), 即将处理计时器
kCFRunLoopBeforeSources = (1UL << 2), 即将处理source
kCFRunLoopBeforeWaiting = (1UL << 5), 即将进入睡眠状态
kCFRunLoopAfterWaiting = (1UL << 6), 进入睡眠状态
kCFRunLoopExit = (1UL << 7), RunLoop退出
kCFRunLoopAllActivities = 0x0FFFFFFFU 所有的状态
*/
所以我们在增加监听器的时候,一般监听的是所有的状态,activity传入的值为kCFRunLoopAllActivities
示例:在子线程中,监听子线程对应的RunLoop的状态
<b style="color:red">
注意:
- 子线程中,需要我们手动获取RunLoop并启动它
- 一个RunLoop中至少有一个source或者timer,如果仅有observer是不行的。
</b>
// 点击屏幕,开启子线程
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 创建线程
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(doSomething) object:nil];
// 启动线程
[thread start];
}
/**
在子线程中,创建RunLoop并添加timer和observer
*/
-(void)doSomething
{
//①获取当前的运行循环--子线程对应的运行循环---如果没有会懒加载创建
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
//②每个运行循环至少要一个source或者timer
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(haha) userInfo:nil repeats:YES];
//③给运行循环添加监听器
//---创建observer-监听所有的状态
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
/**
* kCFRunLoopEntry = (1UL << 0), 当RunLoop启动的时候
kCFRunLoopBeforeTimers = (1UL << 1), 即将处理计时器
kCFRunLoopBeforeSources = (1UL << 2), 即将处理source
kCFRunLoopBeforeWaiting = (1UL << 5), 即将进入睡眠状态
kCFRunLoopAfterWaiting = (1UL << 6), 进入睡眠状态
kCFRunLoopExit = (1UL << 7), RunLoop退出
kCFRunLoopAllActivities = 0x0FFFFFFFU 所有的状态
*/
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"当RunLoop启动的时候");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即将处理计时器");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即将处理source");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即将进入睡眠状态");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"进入睡眠状态");
break;
case kCFRunLoopExit:
NSLog(@"RunLoop退出");
break;
}
});
//---添加监听器
CFRunLoopAddObserver(runLoop, observer, kCFRunLoopCommonModes);
//④启动运行循环
CFRunLoopRun();
}
<b style="color:orange">
相关函数解析:
1 创建观察者 CFRunLoopObserverCreateWithHandler
第一个参数:分配方式(可以理解为内存空间分配方式)-使用默认的 CFAllocatorGetDefault()
第二个参数:需要监听RunLoop的哪些状态--一般监听所有状态 kCFRunLoopAllActivities
第三个参数:是否持续监听
第四个参数:优先级--一般传0
第五个参数:当监听的状态发生改变时调用,回调block
2 添加观察者 CFRunLoopAddObserver
第一参数:运行循环
第二个参数:观察者
第三个参数:添加的模式,一般选择kCFRunLoopCommonModes
3 启动运行循环 CFRunLoopRun()
</b>
9 常驻线程
正常情况下,子线程创建后,当子线程对应的任务执行完毕后,子线程就会被销毁。假设有这样的需求:启动某个子线程,这个子线程一致存在,我们可以随时通过点击按钮(或者其他source)在该线程上面执行其他任务。这样的子线程就是常驻线程。
<b style="color:red">
实现的原理:当创建子线程后,就开启一个运行循环,因为运行循环是一个死循环,所以该线程就会一直常驻,不会被销毁。
注意:为了保证子运行循环不会退出循环,至少需要一个source或者timer。
</b>
10 autoreleasepool在什么时候销毁和创建
第一次创建: 启动RunLoop的时候
最后一次销毁:RunLoop退出的时候
因为自动释放池可能会在某个时间清理一下,所以在整个程序运行过程中,还可能出现创建和时候autoreleasepool。一般是在RunLoop即将进入休眠时,销毁。即将唤醒的时候创建。