你了解 RunLoop 线程保活吗?已封装好,2 句代码直接使用
Posted 程序员大咖
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你了解 RunLoop 线程保活吗?已封装好,2 句代码直接使用相关的知识,希望对你有一定的参考价值。
????????关注后回复 “进群” ,拉你进程序员交流群????????
(温馨提示:这里是一步一步探究,步骤过程比较多,如嫌弃啰嗦,可直接拿后面封装的代码直,2句即可完美使用.)
我们面试中经常遇到很多面试官,问我们关于RunLoop的知识点,可能我们大多数人了解RunLoop,但在项目中,我们真正用到RunLoop还是比较少的,RunLoop其实应用场景还是比较多,比如我们的定时器、线程保活、性能优化、监控应用卡顿.这个博客主要介绍的是'线程保活'
现在的网络请求基本都是用的AFNetworking这个框架,相信大家对它很了解,而它里面就是用RunLoop来控制子线程的生命周期.它会让子线程一直存在内存中不释放,这种好处就是对于我们经常去子线程做事情的话,我们就不必一直去创建-销毁-创建-销毁,这样能很大的提高性能,好处也是多多.
接下来我们来看看怎么能做到控制一个线程的生命周期(也就是线程保活),想让它活多久就多久,想让它什么时候销毁就什么时候销毁.
还是老样子,由简单到复杂,我们创建一个线程,这个用NSThread为例子(也可以用PThread,Operation,GCD都可以)
为了能看到线程是什么时候销毁的,我们可以定一个MyThread,继承NSThread,
@interface MyThread : NSThread
并在.m文件,主要监测线程啥时候销毁.
-(void)dealloc{
NSLog(@"%s",__func__);
}
这样我们就能很清楚的看到MyThread啥时候销毁:
很清楚,上面的代码执行完NSThread就挂了.所以我们在开发中如果有需要经常在子线程干事情,是不是就是希望这个线程能一直不销毁,这样就避免了一直创建-销毁-创建-销毁.
那我们怎么处理?直接在子线程加一个runloop,因为我们知道在获取runloop的时候,就会创建runloop
还是没能成功,如果你有细看我之前的2篇博客,你会注意到有一点:
如果Mode里面没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
所以我们只要在runloop里面加上任何一个就能保证线程不退出,那我们立刻试试:
因为是子线程,我们只用NSDefaultRunLoopMode就行了,不用考虑另一个模式
我们再想想,run方法只是达到了线程保活的功能,真正要执行的应该还是在这个线程上去执行另外的操作.所以我完整的写一个线程保活的例子,如下:
上面就是一个简单的线程保活的例子,其实这个是存在一些弊端的,那我们继续探究一下.
'线程保活'存在的问题再探究
为了代码简洁易懂,我换一个block创建,跟上面的效果是一模一样,便于理解,我先是创建2个控制器的跳转,因为我想看看,控制器销毁的时候,线程会不会也跟着销毁.请看下面的代码:
看控制台很明显,此时并没有达到我们想要的结果,虽然线程确实一直存在,能一直帮我做事情(控制台可以一直输出test),但是我们发现控制器销毁了,而控制器上的线程却没有跟着销毁!我们看不到控制台有输出.那是怎么回事呢?难道出现循环引用?很明显不是!
难道是self.Mythread没有清空?你把self.MyThread = nil;写在VC的dealloc里面,你会发现,线程依然不会销毁!
原因是线程一直没有执行完,它一直卡在[[NSRunLoop currentRunLoop] run]这段代码,所以NSLog(@"---- end ----")也一直没有执行,线程都没有结束,所以它不会销毁.所以如果你想要一个全局的线程,任何页面都可以调用,永远不用销毁的话,那这个线程就能达到这个效果.现在我们想要的效果肯定是在当前页面存在,这个VC销毁的时候,线程也会销毁,我们想控制这个线程的生命周期,想让它销毁它就销毁.那我们继续看看怎么处理.
我们知道线程执行结束,线程就会销毁,只要执行NSLog(@"---- end ----"),就能让线程销毁,所以我们明显的思路就是在VC的dealloc里面停止self.MyThread线程的RunLoop.请看下面的代码.
线程依旧没有销毁.
有可能有的人会认为,这很有可能是执行dealloc说明VC快销毁了,你在快销毁的VC中执行stop方法,是不是来不及呢?那这样,我用按钮执行stop事件.请看下图
此时发现,线程依旧没有销毁(log中没有输出线程的dealloc).
[[NSRunLoop currentRunLoop] run]无限循环;
其实是这个[[NSRunLoop currentRunLoop] run];这个原因导致的,我们先看官方的解释,对于这个方法.
看这个上面红色的翻译大致意思是:这个方法是无限循环的执行runMode:beforeDate,它是很有效的处理一个无限循环的.大概类似
white(1)
{runMode:beforeDate}
所以我们大概知道,这个方法是无限循环也就是关不了,可以理解为死循环.而我们执行的CFRunLoopStop(CFRunLoopGetCurrent()),其实只是停止它无限循环中的一次runMode:beforeDate,因为它会不断创建,所以无法全部停止,所以我们很自然想到用runMode:beforeDate这个方法来尝试解决.
看控制台的输出,我们确实是完成了想用它就用它,不用它就让它销毁的操作,也完成了控制器销毁时候,线程也会销毁.(Stop是一个BOOL值,[NSDate distantFuture]是一个很大的值.)
但是上面的还是不够完美,我们看看同样的代码,假如我们其他操作会不会出现什么问题,比如我们很可能不会点击停止,直接点返回,此时我们也想销毁线程,所以我们想着在VC的dealloc里面调用stop方法试试,请看下面:
看上面的操作,此时应该控制器销毁,而线程依旧还是没有销毁
为什么点击调用stop就可以使线程销毁,而在dealloc里面调用stop就不能使线程销毁?
我们当前这个写法先看一个注意点:waitUntilDone这个传值如果传NO有时候可能导致崩溃,原因如下:
有可能出现的情况
所以上面的参数我改成YES.
而线程不销毁的原因是这样:
再执行到这边的时候,while()里面的条件还是一直是YES,导致还是会一直重复执行里面代码,所以runloop还是会一直运行.所以我们把条件改一下即可: while(!weakSelf.Stop&& weakSelf) 把条件改成这个即可(如果这里用**__strong**处理可能产生循环引用).
看运行结果,这种操作完美解决!
我们再验证一下之前点击停止的那种模式有没有影响!你会发现又崩了!如下:
原因很简单:点击停止已经调用了stop,RunLoop已经结束了,它已经不能做事情了,只是没有销毁.你返回的时候dealloc又调用了stop,又让它去工作,肯定会出问题的.所以我们只要加一个判断即可:
这下真的完美解决了,如论怎么操作,线程和控制器都会销毁!我们看一下成果:
结果完美展示
这里我们发现很多步骤才出来结果,而且是感觉还有点麻烦,那我们直接封装即可:
线程的封装:
封装用起来就非常容易了.先看执行调用代码,再看封装
封装以后就剩这3句,初始化,调用做事,停止,代码很少,很好用,请看效果:
完美解决了这个问题.请看下面封装代码:(就是把我们之前写的,封装起来了):
到这里,我们基本把要说的都说了,该封装的已经封装了,有需要可以直接拿去调用!
因为理论上控制器销毁了,线程也会跟着销毁,所以控制的dealloc里面应该是不用调用stop,按照这个思路我们直接在GDThread的dealloc里面调用stop方法即可,那封装的线程将变得更简单,只有2步操作即可.,初始化,调用做事!
2句代码完成保活!
拓展--C语言的封装
有上面的封装其实已经可以,用C语言的封装作为了解一下:
只要换了红色圈圈的内容即可,其他的都和原来一样,这里也是可以直接使用.
转自:掘金 GDCoder
https://juejin.cn/post/6951397884264710151
-End-
最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!
点击????卡片,关注后回复【面试题
】即可获取
在看点这里好文分享给更多人↓↓
以上是关于你了解 RunLoop 线程保活吗?已封装好,2 句代码直接使用的主要内容,如果未能解决你的问题,请参考以下文章