(iOS干货)关于RunLoop的详细整理

RunLoop

1.RunLoop的基本作用

  • 1.保持程序的持续运行
  • 2.处理app中的各种事件(比如触摸事件、定时器事件、selector事件
  • 3.节省CPU资源,提高程序性能,有事情就做事情,没事情就休息

2.RunLoop与线程

  • 1.关系:一个Runloop对应着一条唯一的线程
  • 2.创建:主线程Runloop已经创建好了,子线程的runloop需要手动创建 创建方式:
NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];

子线程创建的runloop还需要手动的开启:

[currentRunloop run];
  • 3.生命周期:Runloop在第一次获取时创建,在线程结束时销毁

2.RunLoop运行模式对NSTimer定时器的影响

  • 1.首先创建一个定时器
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(task) userInfo:nil repeats:YES];

这里的task为一个普通的打印操作,这时候往控制器的view里拖一个Text View,

  • 2.把定时器对象添加到runloop中,并且制定runloop的运行模式为默认:只有当runloop的运行模式为NSDefaultRunLoopMode的时候定时器才工作,也就是说这时候如果滑动Text View,定时器就不工作了
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
  • 3.如果想在滚动Text View的时候,定时器也工作,可以:
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

但是如果这样做的话,当我们停止滚动的时候定时器又不工作了

  • 4.有时候我们需要在默认情况下以及在滚动的时候都让定时器工作,这时候我们就可以:
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
  • 由此可见:NSRunLoopCommonModes = NSDefaultRunLoopMode & UITrackingRunLoopMode

  • 拓展:①scheduledTimerWithTimeInterval方法:创建定时器并默认添加到当前线程的Runloop中指定默认运行模式

  • ②timerWithTimeInterval:创建定时器,如果该定时器要工作还需要添加到runloop中并指定相应的运行模式

  • 通过以上代码我们不难看出,NSTimer的定时器是受运行模式影响的,,而开发中我们有时候彻底去除这种影响,很显然,NSTimer定时器不能做到这点,这时,我们可以使用GCD的定时器。

GCD定时器

GCD定时器是不受运行模式的影响的,因此,以后尽量使用此定时器,,该定时器的具体参数如下所示:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //01 创建定时器对象
    /*
     第一个参数:是一个宏,表示要创建的是一个定时器
     第二个参数和第三个参数:默认总是传0 描述信息
     第四个参数:队列  决定代码块(dispatch_source_set_event_handler)在哪个线程中调用(主队列-主线程)
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));

    //02 设置定时器
    /*
     第一个参数:定时器对象
     第二个参数:定时器开始计时的时间(开始时间)
     第三个参数:设置间隔时间 GCD的时间单位:纳秒
     第四个参数:精准度
     */
    //这句代码是设置开始两秒之后再调用
    dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC);
    dispatch_source_set_timer(timer, t, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);

    //03 GCD定时器时间到了之后要执行的任务
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"GCD----%@",[NSThread currentThread]);
    });

    //04 默认是在挂起状态,需要手动恢复执行
    dispatch_resume(timer);

    //如果没有一个强指针指向,一创建就回被释放。
    self.timer = timer;

}

CFRunLoopModeRef

  • a.基本说明:
  • CFRunloopModeRef代表着Runloop的运行模式
  • 一个Runloop中可以有多个mode,一个mode里面又可以有多个source\observer\timer等等
  • 每次runloop启动的时候,只能指定一个mode,这个mode被称为该Runloop的当前mode
  • 如果需要切换mode,只能先退出当前Runloop,再重新指定一个mode进入
  • 这样做主要是为了分割不同组的定时器等,让他们相互之间不受影响
  • b.Model的分类:
  • kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
  • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动
  • UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
  • GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
  • kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode

CFRunloopSourceRef

分类(根据函数调用栈来区分):

  • 1.Source0:非基于Port的 :凡是用户主动触发事件都是Source0事件
  • 2.Source1:基于Port的:凡是系统事件都是Source1事件

CFRunLoopObserverRef

作用:监听运行循环的状态

selecter事件与RunLoop之间的关系

  • 默认情况下,selecter事件是被添加到当前的runloop中执行的,并且指定了运行模式为默认,由此可见,performSelecter事件是受运行模式的影响的,仔细查看以下代码,看看有什么问题:
    [NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil];

    -(void)task{
    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"Snip20160713_9"] afterDelay:3.0];
    NSLog(@"+++++");
}

一个显而易见的问题就是我们在子线程中来设置显示图片,然而抛开这个问题不管,图片依旧不会被设置上去,因为在task中缺少一个运行循环,我们需要手动开启一个子运行循环才可以。

继续查看一下代码,图片会被设置到imageView上面吗?

    [NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil];

    -(void)task{
    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runloop run];

    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"Snip20160713_9"] afterDelay:3.0];
    NSLog(@"+++++");
}

答案是否定的,因为我们虽然开启了子运行循环,但是当我们开启这个循环的时候,当前循环里既没有source事件(包括timer事件),也没有selecter事件,于是循环立刻就退出了。正确的书写方式如下:

    [NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil];

    -(void)task{
    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"Snip20160713_9"] afterDelay:3.0];
      NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runloop run];
    NSLog(@"+++++");
}

注意:runLoop是一个死循环,因此++++是不会打印的

使用RunLoop实现常驻线程

众所周知,我们手动开启的子线程在执行完任务之后就会销毁,而有时候我们需要一个子线程在执行完当前任务后,不要销毁,等我们需要的时候再来执行其它任务,这就用到了常驻线程。

  • 假设我们又这样一个需求,但我们点击按钮1的时候会开启一条子线程来执行run1任务,当我们点击按钮2的时候,再让刚才的线程来执行run2任务,具体实现代码如下:
- (IBAction)btn1:(id)sender {
    //01 创建线程,执行任务
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run1) object:nil];

    //02 执行任务
    [thread start];

    self.thread = thread;
}

为了不让刚开启的线程销毁,我们需要给它添加一个运行循环,保证它不释放:

-(void)run1
{
    NSLog(@"run1---%@",[NSThread currentThread]);
    //001 获得当前线程对应的runloop对象
    NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];

    //002 为runloop添加input soucre或者是timer souce或selecter事件(最好就是一个基于端口的事件,这样就不会去执行不必要的方法)
    [currentRunloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];

//以下为其它保证程序运行的方案,不推荐使用。
    //[self performSelector:@selector(run3) withObject:nil afterDelay:3.0];
    //[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];

    //003 启动runloop
    //runUntilDate |run 内部都指定了运行模式为默认
    [currentRunloop run];

}

按钮2:

- (IBAction)goOnBtnClick:(id)sender {
    //让之前创建的线程继续执行
    [self performSelector:@selector(run2) onThread:self.thread withObject:nil waitUntilDone:YES];
}

-(void)run2
{
    NSLog(@"run2---%@",[NSThread currentThread]);
}

RunLoop的自动释放池

runloop的自动释放池什么时候创建释放?

    1. 当runloop进入的时候会创建一个自动释放池。
  • 2)当runloop退出的时候会把之前的自动释放池销毁。
  • 3)当runloop即将进入休眠的时候会把之前的自动释放池先销毁,然后创建一个新的自动释放池。
(iOS干货)关于RunLoop的详细整理_第1张图片
720984-20151203120852377-618169617.png

你可能感兴趣的:((iOS干货)关于RunLoop的详细整理)