Runloop学习
1.RunLoop是什么东西?
runloop:运行循环,官方文档中给出来的描述如下:
- RunLoop对象处理来自窗口系统、端口对象和NSConnection对象的鼠标和键盘事件等源的输入。RunLoop对象还处理计时器事件。
- 您的应用程序既不创建也不显式地管理RunLoop对象。每个线程对象都有一个RunLoop对象,根据需要自动为其创建。如果需要访问当前线程的运行循环,可以使用类方法current来访问。
- 注意,从RunLoop的角度来看,计时器对象不是“输入”——它们是一种特殊类型,这意味着它们在触发时不会导致run循环返回。
一般情况下我们几乎都很少接触到这个东西,但是runloop却又是和开发密切相关的;每一个iOS的APP在启动是都会启动一个runloop,就在main.m文件的入口函数里面:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
//在UIApplicationMain(),函数的内部,其实已经启动了一个runloop
int main(int argc, char * argv[]) {
@autoreleasepool {
// return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"appstart");
int num = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"num:%d",num);
return num;
}
}
//当我们点进去UIApplicationMain()函数,我们可以发现它是返回int类型的
//所以我们把函数改写成上述模式,可以发现NSLog(@"num:%d",num); 永远不会被执行
2.如何访问RunLoop 框架?
在iOS中有2套API可以用来访问RunLoop
Foundation & Core Foundation
NSRunLoop & CFRunLoopRef
NSRunLoop & CFRunLoopRef 都代表RunLoop对象
NSRunLoop 是基于CFRunLoopRef进行了OC的包装;
从上图我们可以看出,runloop 接收事件来自两种不同的源,两种源都使用某一特定的处理例程来处理到达的事件:
1.输入源(input source)
输入源传递异步事件,通常消息来自于其他线程或程序;
2.定时源(timer soucre)
定时源则传递同步事件,发生在特定的时间或者重复的时间间隔.
3.线程与RunLoop的关系
- 线程与RunLoop是一一对应的关系,每一个线程都有一个唯一与其对应的RunLoop对象
- 主线程的RunLoop是已经创建好了的,就在UIApplicationMain()这个方法里面,子线程的RunLoop需要主动创建,并且要手动调用run方法启动一下;
- RunLoop在第一次获取的时候创建,在其对应线程结束的时候销毁
3.1如何获得RunLoop对象?
- Foundation框架,获取RunLoop的方式
//获取当前线程的RunLoop
NSRunLoop *currentRP = [NSRunLoop currentRunLoop];
//获取主线程的RunLoop
NSRunLoop *mainRP = [NSRunLoop mainRunLoop];
*Core Foundation框架,获取RunLoop的方式
//获取当前线程的runloop
CFRunLoopRef currentRP = CFRunLoopGetCurrent();
//获取主线程的runloop
CFRunLoopRef mainRP = CFRunLoopGetMain();
- NSRunLoop 转化成 CFRunLoopRef
NSRunLoop *rp = [NSRunLoop currentRunLoop];
CFRunLoopRef cp = rp.getCFRunLoop;
3.2 在子线程中创建runloop对象:
-(void)viewdidload{
[super viewdidload];
NSThread *td = [[NSThread alloc] initWithTarget:self selector:@selector(log:) object:@"aaa"];
td.name = @"aaa";
[td start];
}
-(void)log{
NSThread *currentTD = [NSThread currentThread];//获取当前线程
NSRunLoop *runlop = [NSRunLoop currentRunLoop];
//获取当前的runloop,如果存在则获取,不存在会自动创建;
}
4. RunLoop的相关类
Core Fundation中关于runloop的5个相关类:
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopTimerRef
- CFRunLoopSourceRef
- CFRunLoopObsverRef
不同的运行模式是为了分隔开不同组的<source/observer/timer> 在运行过程中不会互相干扰;
4.1 CFRunLoopModeRef
- CFRunLoopModeRef表示RunLoop的运行模式,一个mode里面有若刚soucre/observer/timer<至少有一个timer或者source>.
- RunLoop有5种运行模式:
1.kCFRunLoopDefaultMode<默认模式>
2.UITrackingRunLoopMode <响应UI的时候是这种模式>
3.UIInitializationRunLoopMode<刚启动app时进入的第一种模式,随后不再用>
4.GSEventReceiveRunLoopMode<接收系统内部事件,通常不用>- kCFRunLoopCommonModes<一种占位的模式,包含第一第二种模式>
- RunLoop启动的时候,只能选择一种模式;
- RunLoop如果要切换mode,需要先退出loop,在重新制定一种mode;
4.2 CFRunLoopTimerRef
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor orangeColor];
self.tv = [[UITextView alloc] init];
[self.view addSubview:self.tv];
[self.tv mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(0);
make.width.mas_equalTo(300);
make.height.mas_equalTo(150);
}];
self.tv.text = @"1;al\ndflkjadlfj\nasdf\nou\nlkej\nr\nl;\nkjoui\nasfljqeroui\ncv;lke\nroizv;l\nkjdf;klja\ndflafadf;jsad;lfjadf1;al\ndflkjadlfj\nasdf\nou\nlkej\nr\nl;\nkjoui\nasfljqeroui\ncv;lke\nroizv;l\nkjdf;klja\ndflafadf;jsad;lfjadf";
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.view endEditing:YES];
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(log:) userInfo:@"abc" repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
-(void)log:(NSString *)str{
NSLog(@"%@---%@",[NSThread currentThread],[NSRunLoop currentRunLoop].currentMode);
}
执行上述代码,在启动了timer的以后,一旦滑动textview,就会停止循环事件.
原因:
在滑动textview的时候,runloop切换了运行模式
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSLog(@"%@---%@",[NSThread currentThread],[NSRunLoop currentRunLoop].currentMode);
//滑动视图的时候,runloop的运行模式是:UITrackingRunLoopMode
}
上文我们已经提到RunLoop如果要切换mode,需要先退出loop,在重新制定一种mode;
如果我们想NSTimer的循环事件能够一直正常运行,则需要给在runloop添加timer的时候指定另一种运行模式:NSRunLoopCommonModes(这种运行模式同时包含了:default和common两种模式)
4.2.1在子线程中创建NSTimer
dispatch_async(dispatch_queue_create(0, 0), ^{
NSTimer *t = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"当前线程:%@,%@",[NSThread currentThread],NSRunLoop.currentRunLoop.currentMode);
}];
[NSRunLoop.currentRunLoop addTimer:t forMode:NSRunLoopCommonModes];
[NSRunLoop.currentRunLoop run];
});
4.3 CFRunLoopSourceRef
-
CFRunLoopSourceRef 指的是runloop的输入源(input source)
runloop_source.png
以前的分类方法:
- Port-Base Sources
- Custom Input Sources
- Cocoa Perform Selector Sources
现在的分类方法:
- Source0 : 非基于端口的事件源(一般是用户主动触发的事件,例如btn的点击,或者使用方法执行某些任务)
- Source1 : 基于端口的事件源(可以理解为是系统调用的一些事件)
4.4 CFRunLoopObsverRef
- CFRunLoopObsverRef 是观察者,能够监听RunLoop的状态改变
- CFRunLoopObsverRef 可以监听是时间点如下:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),//即将进入loop
kCFRunLoopBeforeTimers = (1UL << 1),//即将处理timer
kCFRunLoopBeforeSources = (1UL << 2),//即将处理source
kCFRunLoopBeforeWaiting = (1UL << 5),//即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6),//刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7),//即将退出loop
kCFRunLoopAllActivities = 0x0FFFFFFFU
}
实现监听
-(void)observer{
/**
创建runloop监听者
参数1:怎么分配空间
参数2:监听哪一种状态 kCFRunLoopAllActivities表示监听素有状态
参数3:是否持续监听
参数4:优先级 总是传0就可以
参数5:监听到状态的回调
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"即将进入runloop");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即将处理input source");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即将处理timer source");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即将进入休眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"runloop被唤醒");
break;
case kCFRunLoopExit:
NSLog(@"runoop退出");
break;
default:
break;
}
});
/**
监听runloop
参数1: 监听的是哪一个runloop
参数2: observer 监听者
参数3: mode 运行模式
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
}