Hello RunLoop

在刚刚接触RunLoop的时候,查过很多的博客,但是可能是自己智力有限的原因,看不懂,看的头都大了,也看不懂。到后来那,看过李明杰老师讲的RunLoop的视频以后,我似乎听懂了,下面我分享一下我的经验总结。

什么是RunLoop

RunLoop就是一个运行循环,在主线程中,RunLoop会默认启动一个RunLoop循环,来保证运行中的程序不会马上退出。RunLoop内部其实是一个do while循环,这个循环不断地转圈来执行一系列的任务或者事件(比如触摸事件、定时器事件、Selector事件等)。当没有任务的时候,RunLoop就会处于休眠的状态。来节省CPU资源,提高程序性能。

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

这里的UIApplicationMain函数内部就启动了一个RunLoop,所以UIApplicationMain函数一直没有返回,保持了程序的持续运行。这个默认启动的RunLoop是跟主线程相关联的。

RunLoop对象
iOS中有两套API来使用RunLoop,一个是Foundation框架中的NSRunLoop,另一个是Core Foundation的CFRunLoopRef,NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API(Core Foundation层面)。
CFRunLoopRef是开源的,您可以在这里下载源码。

RunLoop与线程

每条线程都有唯一的一个与之对应的RunLoop对象,也就是说他们是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。

获取RunLoop对象
在Foundation框架中可以通过下面两个方法获取线程对象。

[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

在Core Foundation框架中可以通过下面两个方法获取线程对象。

CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象

RunLoop中的类

RunLoop中有五个类,他们分别是:

CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

其中 CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装。他们的关系如下:
这里写图片描述
一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer;
每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。
如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

下面先介绍一下Mode,我们就通过RunLoop中的Mode来研究一下NStimer,探究其中的关系。

    NSTimer * timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

    //UITrackingRunLoopMode  当滑动scrollview的时候,timer才会运行;
    //NSDefaultRunLoopMode   当滑动scrollview的时候, timer不会运行;
    //NSRunLoopCommonModes   在以上两种情况下,timer都会运行。其原因就是,当scrollview不滑动时,RunLoop就会自动切换到NSDefaultRunLoopMode模式下,当scrollview滑动时,RunLoop就会切换到UITrackingRunLoopMode模式下。
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

顺便说一下下,NStimer所计算的时间有的时候是不精确的,RunLoop为了节省资源,并不会在非常准确的时间点回调这个Timer。Timer 有个属性叫做 Tolerance (宽容度),标示了当时间点到后,容许有多少最大误差。

下面介绍一种比较精确的定时方法,那就是GCD定时的方法:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //1.获取队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    //2.创建一个定时器(dispatch_source_t本质还是个OC对象);
    //使用属性是为了保证timer不会被释放;
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

    //3.设置定时器的属性;
    // 几时开始任务,每隔多长时间执行一次)
    // GCD的时间参数,一般是纳秒(1秒 == 10的9次方纳秒)
    // 何时开始执行第一个任务
    // dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC) 比当前时间晚3秒
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (ino64_t)(1.0*NSEC_PER_SEC));
    uint64_t interval = (uint64_t)(1.0*NSEC_PER_SEC);
    dispatch_source_set_timer(self.timer, start, interval, 0);

    //4.设置回调;
    dispatch_source_set_event_handler(self.timer, ^{
        NSLog(@"------------%@", [NSThread currentThread]);
    });
    //5.启动定时器;
    dispatch_resume(self.timer);
}

猜你喜欢

转载自blog.csdn.net/Yin_Xian/article/details/51785563