你了解RunLoop线程保活吗?已封装好,2句代码直接使用

如果你没有了解RunLoop的一些基础,建议你看看这2篇博客,对线程保活本质理解有很大帮助

中高级iOS必备知识点之 RunLoop(一)

源码解读RunLoop,理解以后面试必加分

(温馨提示:这里是一步一步探究,步骤过程比较多,如嫌弃啰嗦,可直接拿后面封装的代码直,2句即可完美使用.)

我们面试中经常遇到很多面试官,问我们关于RunLoop的知识点,可能我们大多数人了解RunLoop,但在项目中,我们真正用到RunLoop还是比较少的,RunLoop其实应用场景还是比较多,比如我们的定时器、线程保活、性能优化、监控应用卡顿.这个博客主要介绍的是'线程保活'

现在的网络请求基本都是用的AFNetworking这个框架,相信大家对它很了解,而它里面就是用RunLoop来控制子线程的生命周期.它会让子线程一直存在内存中不释放,这种好处就是对于我们经常去子线程做事情的话,我们就不必一直去创建-销毁-创建-销毁,这样能很大的提高性能,好处也是多多.

接下来我们来看看怎么能做到控制一个线程的生命周期(也就是线程保活),想让它活多久就多久,想让它什么时候销毁就什么时候销毁.

还是老样子,由简单到复杂,我们创建一个线程,这个用NSThread为例子(也可以用PThread,Operation,GCD都可以)

为了能看到线程是什么时候销毁的,我们可以定一个MyThread,继承NSThread,

@interface MyThread : NSThread

并在.m文件,主要监测线程啥时候销毁.

-(void)dealloc{

    NSLog(@"%s",__func__);

}

这样我们就能很清楚的看到MyThread啥时候销毁:

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

很清楚,上面的代码执行完NSThread就挂了.所以我们在开发中如果有需要经常在子线程干事情,是不是就是希望这个线程能一直不销毁,这样就避免了一直创建-销毁-创建-销毁.

那我们怎么处理?直接在子线程加一个runloop,因为我们知道在获取runloop的时候,就会创建runloop

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

还是没能成功,如果你有细看我之前的2篇博客,你会注意到有一点:

如果Mode里面没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出

所以我们只要在runloop里面加上任何一个就能保证线程不退出,那我们立刻试试:

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

  因为是子线程,我们只用NSDefaultRunLoopMode就行了,不用考虑另一个模式

我们再想想,run方法只是达到了线程保活的功能,真正要执行的应该还是在这个线程上去执行另外的操作.所以我完整的写一个线程保活的例子,如下:

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

上面就是一个简单的线程保活的例子,其实这个是存在一些弊端的,那我们继续探究一下.

'线程保活'存在的问题再探究

为了代码简洁易懂,我换一个block创建,跟上面的效果是一模一样,便于理解,我先是创建2个控制器的跳转,因为我想看看,控制器销毁的时候,线程会不会也跟着销毁.请看下面的代码:

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

看控制台很明显,此时并没有达到我们想要的结果,虽然线程确实一直存在,能一直帮我做事情(控制台可以一直输出test),但是我们发现控制器销毁了,而控制器上的线程却没有跟着销毁!我们看不到控制台有输出.那是怎么回事呢?难道出现循环引用?很明显不是!

难道是self.Mythread没有清空?你把self.MyThread = nil;写在VC的dealloc里面,你会发现,线程依然不会销毁!

原因是线程一直没有执行完,它一直卡在[[NSRunLoop currentRunLoop] run]这段代码,所以NSLog(@"---- end ----")也一直没有执行,线程都没有结束,所以它不会销毁.所以如果你想要一个全局的线程,任何页面都可以调用,永远不用销毁的话,那这个线程就能达到这个效果.现在我们想要的效果肯定是在当前页面存在,这个VC销毁的时候,线程也会销毁,我们想控制这个线程的生命周期,想让它销毁它就销毁.那我们继续看看怎么处理.

我们知道线程执行结束,线程就会销毁,只要执行NSLog(@"---- end ----"),就能让线程销毁,所以我们明显的思路就是在VC的dealloc里面停止self.MyThread线程的RunLoop.请看下面的代码.

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

线程依旧没有销毁.

有可能有的人会认为,这很有可能是执行dealloc说明VC快销毁了,你在快销毁的VC中执行stop方法,是不是来不及呢?那这样,我用按钮执行stop事件.请看下图

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

此时发现,线程依旧没有销毁(log中没有输出线程的dealloc).

[[NSRunLoop currentRunLoop] run]无限循环;

其实是这个[[NSRunLoop currentRunLoop] run];这个原因导致的,我们先看官方的解释,对于这个方法.

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

看这个上面红色的翻译大致意思是:这个方法是无限循环的执行runMode:beforeDate,它是很有效的处理一个无限循环的.大概类似

white(1)

{runMode:beforeDate}

所以我们大概知道,这个方法是无限循环也就是关不了,可以理解为死循环.而我们执行的CFRunLoopStop(CFRunLoopGetCurrent()),其实只是停止它无限循环中的一次runMode:beforeDate,因为它会不断创建,所以无法全部停止,所以我们很自然想到用runMode:beforeDate这个方法来尝试解决.

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

看控制台的输出,我们确实是完成了想用它就用它,不用它就让它销毁的操作,也完成了控制器销毁时候,线程也会销毁.(Stop是一个BOOL值,[NSDate distantFuture]是一个很大的值.)

但是上面的还是不够完美,我们看看同样的代码,假如我们其他操作会不会出现什么问题,比如我们很可能不会点击停止,直接点返回,此时我们也想销毁线程,所以我们想着在VC的dealloc里面调用stop方法试试,请看下面:

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

看上面的操作,此时应该控制器销毁,而线程依旧还是没有销毁

为什么点击调用stop就可以使线程销毁,而在dealloc里面调用stop就不能使线程销毁?

我们当前这个写法先看一个注意点:waitUntilDone这个传值如果传NO有时候可能导致崩溃,原因如下:

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

有可能出现的情况

所以上面的参数我改成YES.

而线程不销毁的原因是这样:

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

再执行到这边的时候,while()里面的条件还是一直是YES,导致还是会一直重复执行里面代码,所以runloop还是会一直运行.所以我们把条件改一下即可: while(!weakSelf.Stop&& weakSelf) 把条件改成这个即可(如果这里用__strong处理可能产生循环引用).

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

看运行结果,这种操作完美解决!

我们再验证一下之前点击停止的那种模式有没有影响!你会发现又崩了!如下:

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

原因很简单:点击停止已经调用了stop,RunLoop已经结束了,它已经不能做事情了,只是没有销毁.你返回的时候dealloc又调用了stop,又让它去工作,肯定会出问题的.所以我们只要加一个判断即可:

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

这下真的完美解决了,如论怎么操作,线程和控制器都会销毁!我们看一下成果:

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

结果完美展示

这里我们发现很多步骤才出来结果,而且是感觉还有点麻烦,那我们直接封装即可:

线程的封装:

封装用起来就非常容易了.先看执行调用代码,再看封装

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

封装以后就剩这3句,初始化,调用做事,停止,代码很少,很好用,请看效果:

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

完美解决了这个问题.请看下面封装代码:(就是把我们之前写的,封装起来了):

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

到这里,我们基本把要说的都说了,该封装的已经封装了,有需要可以直接拿去调用!

因为理论上控制器销毁了,线程也会跟着销毁,所以控制的dealloc里面应该是不用调用stop,按照这个思路我们直接在GDThread的dealloc里面调用stop方法即可,那封装的线程将变得更简单,只有2步操作即可.,初始化,调用做事!

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

2句代码完成保活!

拓展--C语言的封装

有上面的封装其实已经可以,用C语言的封装作为了解一下:

你了解RunLoop线程保活吗?已封装好,2句代码直接使用

只要换了红色圈圈的内容即可,其他的都和原来一样,这里也是可以直接使用.

接下来我会继续努力编写其他博客,您的支持就是我最大的动力!

如果觉得我写得对您有所帮助,请点赞关注我,我会持续更新

上一篇:中高级iOS必备知识点之 RunLoop(一)


下一篇:iOS - RunLoop总结 by:nixs