Runloop学习

2019-08-14  本文已影响0人  tp夕阳武士

1.RunLoop是什么东西?

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



一般情况下我们几乎都很少接触到这个东西,但是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文档

RunLoop.png

从上图我们可以看出,runloop 接收事件来自两种不同的源,两种源都使用某一特定的处理例程来处理到达的事件:
1.输入源(input source)

输入源传递异步事件,通常消息来自于其他线程或程序;

2.定时源(timer soucre)

定时源则传递同步事件,发生在特定的时间或者重复的时间间隔.

3.线程与RunLoop的关系

3.1如何获得RunLoop对象?
//获取当前线程的RunLoop
 NSRunLoop *currentRP = [NSRunLoop currentRunLoop];
//获取主线程的RunLoop
 NSRunLoop *mainRP = [NSRunLoop mainRunLoop];

*Core Foundation框架,获取RunLoop的方式

//获取当前线程的runloop
CFRunLoopRef currentRP =  CFRunLoopGetCurrent();
    
//获取主线程的runloop
CFRunLoopRef mainRP =  CFRunLoopGetMain();
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个相关类:


runloop与runloopmode的关系.png

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

4.1 CFRunLoopModeRef
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];
 });

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

4.3 CFRunLoopSourceRef

以前的分类方法:

现在的分类方法:

4.4 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
上一篇下一篇

猜你喜欢

热点阅读