对 runloop 的理解
为什么需要runloop
一般来说线程是做完事情,就生命结束了,给它续命的办法就是让它 sleep,runloop 也是这样干的
runloop能处理的事情分为4种:
- source0:自定义事件源
- source1:基于port的事件源
- timer:定时器
- 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(@"自定义代码"); 只是一句代码,它不是任务