对 runloop 的理解

为什么需要runloop

一般来说线程是做完事情,就生命结束了,给它续命的办法就是让它 sleep,runloop 也是这样干的

runloop能处理的事情分为4种:

  1. source0:自定义事件源
  2. source1:基于port的事件源
  3. timer:定时器
  4. block:GCD 分配的任务

解释下 source0 的使用:

  • 创建一个source0实例,source0持有一个perform函数
  • 把source0加入到工作线程的runloop中
  • 某对象持有source0
  • 当某些事件发生时,对象将source0状态设置为待处理,唤醒或者不唤醒工作线程都可以
  • 工作线程被唤醒之后,处理source0,也就是执行source0的perform函数

这样一系列的操作后,就可以不定时地在指定的线程中做一些事情了。

source1 与 source0 类似,不过它触发的时机是由系统的端口触发的。

那为什么需要搞这么复杂,无非就是想在指定的线程做事情嘛?

以下是我的猜测:

让指定线程执行某段代码,分为两种情况:

(1)指定的线程工作已经做完了,那它就已经结束生命了,没有办法执行了,为了解决这个问题,runloop就要在没有事情做时休眠

(2)指定的线程工作还没有完成,那我们就可以在这个线程的任务后面添加任务了吗?

不行的,因为没有经过特殊处理的线程是在执行代码,而不是任务,任务是GCD中的概念。

比如:我创建一个线程,并让这个线程执行以下方法:

[NSThread detachNewThreadSelector:@selector(doit) toTarget:self withObject:nil];
func doit() {
       let x = 0  
}

当doit方法 return 了,线程的事情也就做完了,是没有办法动态添加任务的。

所以iOS选择的解决办法是在runloop中提前埋一个口子

当然 GCD 的 block任务就是另一种方式啦

source0 与 source1 的区别

它们针对的场景是不一样,source0 是给同一个进程内的其他线程调用的,这种调用是不需要经过操作系统的,source1 是给其他进程调用的,这种调用必须通过操作系统,所以就只能通过端口了。

selector是使用source0和timer实现的

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self performSelectorOnMainThread:@selector(asdf) withObject:nil waitUntilDone:NO];
    });

在方法 asdf 中打断点,结果如下:

 

 

[self performSelector:@selector(asdf) withObject:nil afterDelay:1];

 

一个很重要的Demo

- (void)inSubThread {
    [self performSelector:@selector(asdf) withObject:nil afterDelay:1];
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    NSLog(@"自定义代码");
}

- (void)asdf {
    NSLog(@"asdf");
}

是“自定义代码”先打印,还是“asdf”先打印?

答案: asdf 先打印

只有runloop退出了流程才能接着走,“自定义代码”才能被打印出来

因为runloop不退出的话,run 方法也就无法 return,不 return 也就没办法接着执行下一行代码

NSLog(@"自定义代码"); 只是一句代码,它不是任务

 

posted @ 2020-06-19 14:57  小Garfield  阅读(282)  评论(0编辑  收藏  举报