1、什么是 RUNLOOP?
RUNLOOP其实就是一个运行循环。基本作用是保持程序的持续运行,处理App的各种事件(比如:selector事件、触摸事件、定时器事件)。好处是节约cup资源,该工作的时候工作,该休息的时候休息。其实RUNLOOP就是相当于以下代码:
int main(int argc , char * argv[]){
BOOL runing = YES;
do {
// 执行各种任务和处理事件
}while(running);
return 0;
}
其实UIApplicationMain中就是启动了一个RUNLOOP循环,UIApplicationMain一直没有返回,以保证程序的持续运行。RUNLOOP默认是与主线程相关的。
2、iOS中如何是访问RUNLOOP?
一种是通过Foundation(NSRunLoop),另一种是通过Core Foundation(CFRunLoopRef)。
这两个都代表着RUNLOOP的对象。 其实NSRunLoop是对CFRunLoopRef的一层OC包装,所以要研究CFRunLoopRef的功能,CFRunLoopRef也是一个开源的框架。
3、RUNLOOP与线程有什么关系?
(1 )每条线程都有唯一的一个与之对应的RUNLOOP对象。
(2)主线程的RUNLOOP已经自动创建好了,子线程的RUNLOOP需要主动创建。
(3)RUNLOOP在第一次获取时创建,在线程结束时销毁。
4、如何获得RUNLOOP对象?
上述提到iOS中有2套API来访问和使用RUNLOOP。NSRnLoop和CFRunLoopRef都代表着RUNLOOP对象。
第一种:Foundation
(1)[NSRunLoop currentRunLoop];// 获得当前线程的RUNLOOP对象
(2)[NSRunLoop mainRunLoop];// 获得主线程的RUNLOOP对象
第二种:CoreFoundation
(1)CFRunLoopGetCurrent();// 获得当前线程的RUNLOOP对象
(2)CFRunLoopGetMain();// 获得主线程的RUNLOOP对象
- (void)viewDidLoad {
[super viewDidLoad];
//
NSRunLoop * mainRunLoop = [NSRunLoop mainRunLoop];
NSRunLoop * currentRunLoop = [NSRunLoop currentRunLoop];
NSLog(@"%@ \n\n %@" , mainRunLoop , currentRunLoop);
//
CFRunLoopRef currentRun = CFRunLoopGetCurrent();
CFRunLoopRef mainRun = CFRunLoopGetMain();
NSLog(@"%@ \n\n %@" , mainRun , currentRun);
//
CFRunLoopRef tranferRunLoop = mainRunLoop.getCFRunLoop;
NSLog(@"%@", tranferRunLoop);
// RunLoop和线程的关系
[[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start];
}
- (void)run {
NSLog(@"%@",[NSThread currentThread]);
// 如何创建子线程的runloop?使用[NSRunLoop currentRunLoop]。这是个懒加载,此方法会判断当前线程有没有存在子线程的runloop,如果没有会创建。
}
5、CoreFoundation中关于RUNLOOP的类有哪些?
下面五个类构成RUNLOOP的所有内容:
(1)CFRunLoopRef:RUNLOOP本身
(2)CFRunLoopModeRef:Mode运行模式
(3)CFRunLoopSourceRef:Source事件源/输入源
(4)CFRunLoopTimerRef:Timer定时器
(5)CFRunLoopObserverRef:Observer监听状态
6、CoreFoundation里面五个类之间的有什么关系?
(1)每一个RUNLOOP有多种运行模式CFRunLoopModeRef。但只能启动选择一种运行模式CFRunLoopModeRef,此时这个Mode被称为CurrentMode。
(2)如果需要切换运行模式CFRunLoopModeRef,只能先退出CFRunLoopRef,再重新选择运行模式CFRunLoopModeRef。目的是分开不同组的CFRunLoopSourceRef、CFRunLoopTimerRef和CFRunLoopObserverRef。
(3)每种运行模式CFRunLoopModeRef至少必须有一个CFRunLoopSourceRef、一个CFRunLoopTimerRef、一个CFRunLoopObserverRef。因为Mode运行模式里面真正执行的就是Source。
(4)CFRunLoopObserverRef只是监听状态,没有实际作用。一般可以在需要的某个状态下执行事件或方法。
7、iOS开发中常见的CFRunLoopModeRef运行模式默认注册有哪五种?
(1)kCFRunLoopDefaultMode:这是App默认的运行模式,通常主线程在这个CFRunLoopModeRef下运行。
(2)UITrackingRunLoopMode:界面跟踪的运行模式,用于UIScrollVIew触摸滑动,保证界面不受其他运行模式影响。
(3)UIInitializationRunLoopMode:界面初始化的运行模式,在第一次启动App时运行该模式,启动完成后不再使用。
(4)GSEventReceiveRunLoopMode:接受系统时间的运行模式,基本用不到。
(5)kCFRunLoopCommonModes:这是一个占位用的运行模式,不是一个真正的运行模式。
8、如何实现在多模式下的正常运行?
第一种方法:通过 forMode: 将RUNLOOP添加进多种Mode。
第二种方法:通过 forMode: 将RUNLOOP添加进多种kCFRunLoopCommonModes。
- (void)run {
// 打印当前线程
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"%@",[NSRunLoop currentRunLoop].currentMode);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
#warning 在主线程中创建runloop
// 1.创建定时器,该方法内部会自动添加到runloop里面,并且设置运行模式为默认模式NSDefaultRunLoopMode。
NSTimer * timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 2.添加定时器到runloop中,第一个参数是定时器,第二个参数是运行模式
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 当拖拽时会进入界面追踪模式,其他的运行模式便不再理会(包括定时器里面的模式)。把手松开时会进入定时器模式。如果需要在界面拖拽是工作,只需要把定时器模式改为UITrackingRunLoopMode界面追踪模式。
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
// 如果要定时器在默认运行模式下和拖拽运行模式下都运行 那么就有两种方法:
// 第一种:
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
// 第二种:在这里 NSRunLoopCommonModes = NSDefaultRunLoopMode + UITrackingRunLoopMode。凡是添加NSRunLoopCommonModes中的时间都会被同时common标签的运行模式上。例如timmer里面就会有NSDefaultRunLoopMode和UITrackingRunLoopMode都被打上common标记。
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
#warning 在子线程中创建runloop
NSRunLoop * currentRL = [NSRunLoop currentRunLoop];
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 子线程的runloop是需要开启的
[currentRL run];
// [currentRL runUntilDate:<#(nonnull NSDate *)#>];
// [currentRL runMode:(nonnull NSString *) beforeDate:<#(nonnull NSDate *)#>];
}
9、CFRunLoopObserverRef是观察者模式监听到RUNLOOP的哪些时间点?
(1)kCFRunLoopEntry:即将进入Loop
(2)kCFRunLoopBeforeTimers:即将处理Timer
(3)kCFRunLoopBeforeSources:即将处理Scource
(4)kCFRunLoopBeforeWaiting:即将进入休眠
(5)kCFRunLoopAfterWaiting:刚从休眠中唤醒
(6)kCFRunLoopExit:即将推出Loop
(7)kCFRunLoopAllActivities:表示所有状态
#import "ViewController.h"
@interface ViewController ()
// 必须要有强引用,不然对象会被释放
@property (nonatomic , strong) dispatch_source_t timer ;
@end
@implementation ViewController
-(void)viewDidLoad {
}
-(void)observe{
// 下面两个runloop添加观察者不是等同的
// [[NSRunLoop currentRunLoop] addObserver:<#(nonnull NSObject *)#> forKeyPath:<#(nonnull NSString *)#> options:<#(NSKeyValueObservingOptions)#> context:<#(nullable void *)#>];
// [CFRunLoopAddObserver(<#CFRunLoopRef rl#>, <#CFRunLoopObserverRef observer#>, <#CFStringRef mode#>)];
// 下面两个runloop添加时间是等同的
// [[NSRunLoop currentRunLoop] addTimer:<#(nonnull NSTimer *)#> forMode:<#(nonnull NSString *)#>];
// [CFRunLoopAddTimer(<#CFRunLoopRef rl#>, <#CFRunLoopTimerRef timer#>, <#CFStringRef mode#>)];
// 1.创建监听者
// 1.1 函数调用的方式监听状态改变
// CFRunLoopObserverRef observer = CFRunLoopObserverCreate(<#CFAllocatorRef allocator#>, <#CFOptionFlags activities#>, <#Boolean repeats#>, <#CFIndex order#>, <#CFRunLoopObserverCallBack callout#>, <#CFRunLoopObserverContext *context#>)
// 1.2 函数调用的模块监听状态改变
// 参数一:怎么分配存储空间,CFAllocatorGetDefault()默认方式
// 参数二:要监听的状态,kCFRunLoopAllActivities表示监听所有状态改变
// 参数三:是否要持续监听,YES持续
// 参数四:优先级
// 参数五:当状态改变时(五种状态),会回调该方法
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
// 判断runloop的五种状态
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"即将进入Loop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即将处理Timer");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即将处理Scource");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即将进入休眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"刚从休眠中唤醒");
break;
case kCFRunLoopExit:
NSLog(@"即将推出Loop");
break;
default:
break;
}
});
// 添加监听者
// 参数一:要监听哪个runloop,CFRunLoopGetCurrent()指的是当前runloop对象
// 参数二:观察者observer
// 参数三:运行模式,不能传OC的运行模式。
// NSDefaultRunLoopModes == kCFRunLoopDefaultModes
// NSRunLoopCommonModes == kCFRunLoopCommonModes
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self observe];
// 两秒钟执行一次task
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(task) userInfo:nil repeats:YES];
}
-(void)task {
NSLog(@"%s",__func__);
//2016-08-30 22:55:34.345 testOne[21518:571296] 刚从休眠中唤醒
//2016-08-30 22:55:34.345 testOne[21518:571296] -[ViewController task]
//2016-08-30 22:55:34.346 testOne[21518:571296] 即将处理Timer
//2016-08-30 22:55:34.346 testOne[21518:571296] 即将处理Scource
//2016-08-30 22:55:34.346 testOne[21518:571296] 即将进入休眠
}
@end
10、RUNLOOP在开发中的使用
(1)开启常驻线程(开发过程中,如果需要保持子线程的生命周期一直处于工作状态)
#import "ViewController.h"
#import "UIButton+Categories.h"
@implementation ViewController{
NSThread * _thread;
}
- (void)viewDidLoad
{
CGFloat margin = 30;
UIButton * keepThreadWork = [UIButton createButtonWithFrame:CGRectMake(100, 100, 120, 30) ImageName:nil selectedImageName:nil Target:self Action:@selector(keepThreadWorkClick) Title:@"保持线程运行" TitleColor:[UIColor blackColor]];
UIButton * otherTask = [UIButton createButtonWithFrame:CGRectMake(100, CGRectGetMaxY(keepThreadWork.frame) + margin, 120, 30) ImageName:nil selectedImageName:nil Target:self Action:@selector(otherTaskClick) Title:@"开启其他事件" TitleColor:[UIColor blackColor]];
[self.view addSubview:keepThreadWork];
[self.view addSubview:otherTask];
}
// 保持线程运行
-(void)keepThreadWorkClick {
_thread = [[NSThread alloc] initWithTarget:self selector:@selector(todoThread) object:nil];
[_thread start];
}
// 开启其他事件
-(void)otherTaskClick {
NSLog(@"----");
}
// 子线程执行事件
-(void)todoThread {
[self performSelector:@selector(todoTask) onThread:_thread withObject:nil waitUntilDone:YES];
}
// 延迟执行的时间
-(void)todoTask {
// [不可取]这样可以让线程不死,但是阻碍其他功能
// while (true) {
NSLog(@"currentThread = %@",[NSThread currentThread]);
// }
// 当前子线程对应的runloop,保持线程的工作状态仅限于子线程!
NSRunLoop * runloop = [NSRunLoop currentRunLoop];
// (1) 一般是网runloop里面添加source
[runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
// (2) 10s内不再运行程序
// [runloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
// [不可取]当前的runloop没有运行模式,需要为其添加timer,作用是为了不让runloop退出
// [runloop addTimer:[NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(add) userInfo:nil repeats:YES] forMode:NSDefaultRunLoopMode];
// 默认没有开启,需要手动开启
[runloop run];
}
//-(void)add {
// NSLog(@"%s",__func__);
//}
@end
(2)控制定时器在某种运行模式下启动。
(3)可以天剑观察者Observer监听RUNLOOP的状态。比如在所有点击之前做事件统计。
(4)控制某种事件在特定的模式下执行。
11、RUNLOOP运行步骤有哪些?
(1)通知观察者Observer,即将进入RUNLOOP;
(2)通知观察者Observer,将要处理Timer;
(3)通知观察者Observer,将要处理事件源Source();
(4)处理事件源Source();
(5)如果有新的事件源Source,则跳转到第(2)步继续运行;
(6)通知观察者Observer,时间快结束了线程即将休眠;
(7)休眠,等待唤醒,这时候可以手动唤醒、Timer定时器唤醒...;
(8)通知观察者Observer,有新事件线程刚被唤醒;
(9)处理唤醒时收到的消息,之后跳转到第(2)步继续运行;
(10)通知观察者Observer,即将推出RUNLOOP。
12、RUNLOOP的自动释放池的作用是什么?
13、什么时候自动释放池什么时候释放?
(1)第一次创建:自动释放池是在RUNLOOP运行步骤的第(1)步时创建,用于装载不需要的变量。
(2)最后一次销毁:RUNLOOP退出的时候,即是在RUNLOOP运行步骤的第(10)步时销毁。
(3)其他时候的创建和销毁:当他即将进入休眠时,会把之前装载的变量全部释放掉,同时创建一个新的自动释放池。休眠意味着所有Timer、Source已经处理完毕了。