本文首发于个人博客
控制线程生命周期(线程保活)
通过上一篇详解RunLoop之源码分析 我们知道了runlLoop每条线程都有唯一的一个与之对应的RunLoop对象 并且 RunLoop会在线程结束时销毁
接下来就具体分析RunLoop会在线程结束时销毁这个特点
首先定义一个YZThread继承自NSThread 重写它的dealloc方法,为了检测是否销毁
然后,创建该类,并执行方法,如下图所示
1 | /** YZThread **/ |
运行结果
1 | Interview03-线程保活[7854:134789] -[ViewController run] <YZThread: 0x600000a13b80>{number = 3, name = (null)} |
从结果中,我们发现,当执行完run这个方法之后,该线程就销毁了。
但是在项目中,或者很多第三方中(比如AFN),可能会遇到这个线程我们经常做事情,如果每次都销毁、创建、销毁、、、那么对性能也是一种损耗.我们就需要我们自己控制线程的销毁和创建。
上面线程之所以立即销毁,根据上篇文章我们知道,
- 是因为 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
那我们给它加上这些东西不就行了么?来试试看吧。
1 | /** YZThread **/ |
运行结果
1 | Interview03-线程保活[7967:136867] -[ViewController run] <YZThread: 0x600003105b00>{number = 3, name = (null)} |
如上所示,添加了port之后,该线程就没有调用dealloc方法了。上一篇介绍了,Source1包含了基于Port的线程间通信,也就是说。添加了port之后,相当于有了source1 那么线程就不会退出,也就不会调用dealloc方法了。
但是如果我们想在这个线程执行自己的操作,那就需要我们持有这个线程了。如下图所示,增加个属性,并且屏幕点击事件中调用test方法
1 |
|
运行结果:
1 | Interview03-线程保活[8035:138457] -[ViewController run] <YZThread: 0x60000232d4c0>{number = 3, name = (null)} |
果然是线程不会销毁,而且 由 number = 3 可知,确实是在我们自己创建的线程中执行的操作
接下来我们再看一个问题,首先在控制器中写上dealloc方法,
1 | - (void)dealloc |
然后给个导航控制器跳转到当前控制器,之后返回的时候发现dealloc并没有执行
这是因为代码中用的
1 | self.thread = [[YZThread alloc] initWithTarget:self selector:@selector(run) object:nil]; |
这个方法,会导致线程持有当前控制器,导致循环引用,如果解除循环引用,需要把这个initWithTarget 换成initWithBlock
我们将之前的
1 | - (void)viewDidLoad { |
改成
1 |
|
可以发现
1 | Interview03-线程保活[8035:138457] -[ViewController dealloc] |
这次控制器可以销毁了。然后,你可能已经发现了,虽然控制器可以销毁,但是线程并没有销毁。换句话说,虽然没有循环引用了。而且控制器销毁了,然后线程依然没有销毁,这是为什么呢。
[[NSRunLoop currentRunLoop] run]
注意[[NSRunLoop currentRunLoop] run]
其实关键的一句代码是
1 | [[NSRunLoop currentRunLoop] run]; |
看一下官方对这个方法的解释
1 | Discussion |
也就是说,NSRunLoop的run方法是无法停止的,它专门用于开启一个永不销毁的线程(NSRunLoop)
就相当于
1 | while (1) { |
就算我们手动调用停止RunLoop的方法,
1 | // 停止RunLoop |
也无法停止这个线程,因为这个只能停止这一次的RunLoop,下次循环,依然可以继续进行下去
我们把代码修改为如下的写法
1 | - (void)viewDidLoad { |
我们用一个bool值做未标记,当下次进来的时候。判断已经停止了。那就不进入下次RunLoop了,就可以退出循环
注意
1 | // 在子线程调用stop(waitUntilDone设置为YES,代表子线程的代码执行完毕后,这个方法才会往下走) |
至此,我们代码如下所示
1 |
|
封装线程
到这里,我们已经可以控制线程的创建,执行,销毁了。
但是,这也太复杂了吧。每次我们都需要写这么多代码,如果我们每次,都这么干的话,岂不是疯掉了。所以我们要抽取出来,这样,以后用的时候,直接拿来就可以了。
首先新建一个继承自 NSObject的类YZPermenantThread 然后持有一个NSThread的属性,注意这里不是直接继承NSThread 是因为,如果继承了NSThread 那么外界调用的时候,可以直接使用NSThread的方法,会影响线程的使用。所以,本着简洁,可控的原则,定义的类继承自NSThread 然后拥有属性NSThread,这样,外界只能使用我们暴露出去接口。
.h中代码如下
1 | typedef void (^YZPermenantThreadTask)(void); |
.m中代码如下
1 | /** YZPermenantThread **/ |
YZPermenantThread.h 就是这样的
1 |
|
YZPermenantThread.m 就是这样的
1 |
|
这样就简单封装了线程,而且因为是用C语言创建的,能更灵活的控制 看官方对CFRunLoopAddSource的定义,第三个参数,如果是false,那么这个线程,执行完当前这次RunLoop,不会退出。这样写的话,就可以保证线程一直存在。
1 |
|
封装之后的使用
使用的时候,我们可以主动调用stop 来销毁掉线程,也可以,不做任何处理,当控制器销毁时候,YZPermenantThread对象就会销毁,根据
1 | - (void)dealloc |
可知,RunLoop也会停止,线程也就退出了。所以使用的时候,如下所示
1 |
|
至此。就说完了RunLoop和线程直接关系,以及线程的保活,以及自定义线程。这样使用起来更加得心用手。
本文相关代码github地址 github
本文参考资料: