Runloop学习

1.RunLoop是什么东西?

runloop:运行循环,官方文档中给出来的描述如下:


  • RunLoop对象处理来自窗口系统、端口对象和NSConnection对象的鼠标和键盘事件等源的输入。RunLoop对象还处理计时器事件。
  • 您的应用程序既不创建也不显式地管理RunLoop对象。每个线程对象都有一个RunLoop对象,根据需要自动为其创建。如果需要访问当前线程的运行循环,可以使用类方法current来访问。
  • 注意,从RunLoop的角度来看,计时器对象不是“输入”——它们是一种特殊类型,这意味着它们在触发时不会导致run循环返回。

一般情况下我们几乎都很少接触到这个东西,但是runloop却又是和开发密切相关的;每一个iOS的APP在启动是都会启动一个runloop,就在main.m文件的入口函数里面:

#import 
#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文档

RunLoop.png

从上图我们可以看出,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
runloop与runloopmode的关系.png

不同的运行模式是为了分隔开不同组的 在运行过程中不会互相干扰;

4.1 CFRunLoopModeRef
  • CFRunLoopModeRef表示RunLoop的运行模式,一个mode里面有若刚soucre/observer/timer<至少有一个timer或者source>.
  • RunLoop有5种运行模式:
    1.kCFRunLoopDefaultMode<默认模式>
    2.UITrackingRunLoopMode <响应UI的时候是这种模式>
    3.UIInitializationRunLoopMode<刚启动app时进入的第一种模式,随后不再用>
    4.GSEventReceiveRunLoopMode<接收系统内部事件,通常不用>
    1. 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 *)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];
 });

基于GCD封装的timer,不受runloopmode影响

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);
}

5.RunLoop 处理逻辑

runloop_逻辑图.png
runloop_逻辑网友图.png
官方版本
官方描述.png

你可能感兴趣的:(Runloop学习)