美文网首页ios 优化iOS-性能
iOS消息高并发优化

iOS消息高并发优化

作者: 大冯宇宙 | 来源:发表于2018-03-21 11:07 被阅读0次

    本篇文章并不打算过多的讲解技术实现的细节,大部分都是点到为止。我个人觉得技术细节虽然很重要,但是它只是实现一个功能的手段,更为重要的是实现功能的思路和方向。只有理清了思路,选定了方向,接下来的实现就应该是水到渠成了。

    好了,下面我们该说说优化的事。我们看一下微信,基础功能就是聊天,能看到经常发生变化的就是消息列表,和聊天时候的聊天界面了,几乎只要你在使用它的时候这两个页面都会发生变化。所以针对性能上最重要的两个界面,一个是消息列表,一个是聊天界面。

    一.消息列表页面的优化:

    首先我们发消息时候观察一下消息列表的特性,当发送一条消息时候,消息的数量会变化,列表会出现在最上边的位置,列表内的内容会发生变化。从消息列表的特性,我们就可以分析出要优化的点了。通过这些点,我们做了一些优化:

    1.如果列表消息从没显示过需要刷新列表,创建好一个cell后,将cell插入到第一位上,cell插入的性能要高于刷新tableview的性能。

    2.如果消息已经显示过了,但是并不是第一位,则需要刷新列表。

    3.如果消息已经显示,并且是第一位,则只需要cell的内容变化。

    4.只修改cell里的内容,不进行刷新cell整体,这里要注意的是,一定要最小化刷新。刷新点越小,性能损耗越小。我们项目架构是MVVM,采用了ReactiveCocoa框架,针对每个cell上的可变化的控件数据进行了监听,每一个cell上对应一个vm,这样当vm上的数据变化时候,cell上的数据也就跟着变了。做到了最小化刷新。

    5.避免使用autolayout计算位置,这个很重要,在性能要求高的情况下,autolayout计算会很耗时间,尤其在算tableview高度的时候可见一斑。可喜的是消息列表的高度是固定的,所以在计算高度时候我们并未花费时间。

    6.使用(__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath而不使用-(nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier来查找cell,因为下边的方法会多查一次,耗时更长一点。

    7.cellForRowAtIndexPath方法只负责创建cell,willDisplayCell方法才给cell进行赋值操作。从方法名字就可以看出来原因

    8.当来消息轰炸时候,必然会是不同的人发来的消息,会导致tableview不可避免的刷新,如果不加处理必然会卡顿,要知道,机器也是有瓶颈的。这里我们做的优化是根据cpu的使用率选择性的刷新tableview。后来我们发现微信也是有这个现象,并不是实时的刷新,我们猜测也是类似处理。

    9.重绘制系统控件,相信你也发现了消息列表里,主要有两个控件,一个是头像,一个是label。而系统的UIImageView用来显示头像,未免有点重。我们的处理是使用UIView,设置View得layer.content来处理。针对layer层做的setImageWithUrl的第三方库也不少,大家可以自行查询。另一个就是label,如果你能集成UIView自己绘制一个label,我想也许会有一点效果。

    以上我们对消息列表做的优化,写的并不全,只记得大概有这些吧。

    二.聊天界面的优化:

    聊天界面的优化算是比较繁琐的了,但是优化点跟回话列表的优化差不多。上边提到回话列表里最耗时的tableview的高度是固定的,而聊天界面的几乎每条消息的高度都可能不一样,所以我们在优化聊天界面时候最重要的一点就是计算tableviewcell的高度。而我们在计算tableview的高度是怎么做的呢?

    其实这个网上千篇一律的优化tableview的方式是大同小异的。

    主要有两个准则:

    第一个是能在后台线程执行的都放在后台线程里。

    第二个计算高度要放在显示之前。

    上边提到我们使用的ReactiveCocoa基于事件流来处理消息,这个确实是为开发带来了很大的好处,处理层次非常清晰,开发效率的提升,代码复杂度降低,多人员开发分工分明,低耦合等这些好处充分让你感受到编程的乐趣,之后我们会出一篇关于架构的文章,这里只是稍提一嘴。当然即使你不用ReactiveCocoa也没关系,思路都是一样的。首先是你从底层异步接收到消息,进行消息的处理,包括数据库的处理,提供给View显示的对象处理,这里我们是用的VM层,其中也包括了消息的高度的计算,这些都是放在线程队列里,当所有的处理完成,然后进行主线程消息列表更新,进行插入一个cell和向上滚动一条消息的距离。我们之前把VM的创建放在了主线程,发现在32位机器上掉了10fps,这是让人不能忍受的事情。当然还有一点是尽量保证你的对象不要太大,如果太大也会影响性能,我们的vm就比较大,这也是以后性能上的隐患。优化tableView的另外一点就是cell的制作了,要说的跟上边第九条提到的也差不多,这里我也总结了一下几个原则:

    1.巧妙的选择控件。比如上边提到的,如果只是显示一个图片,那么用View的layer.content性能自然是好一些。图片加点击也可以用view,然后监听view的touch事件,像button这种重量级的控件在性能为主的app面前,我对他们都是弃之如敝履。

    2.减少使用layer层的cornerRadius,mask等圆角的绘制,这会引发离屏渲染,增加cpu的占用率。如果业务需要的话,我们可以通过UIBezierPath来drawInRect它。

    3.避免设置透明

    4.避免autolayout设定控件位置

    5.尽可能的减少视图的层级,如果你能把所有的控件都绘制到一个View上,可想而知性能会爆棚。

    三.其他优化

    还有两大优化点不容忽视。

    一是数据持久层的优化,就是所谓的数据库优化。

    二就是内存优化。

    关于持久层的优化严格来性能上通常不会有太大出入,只不过我们之前是直接针对底层sqlite数据库很简单的封装,后来用了fmdb,发现在插入性能上有了明显的提升。几乎用过的小伙伴都说好。持久化这边我们将fmdb,jsonModel进行封装了一个库,提供了友好的api,通常像这样使用[message findAll],[message save]等基本上都不需要写sql语句。回头我们会开源化。关于sql优化的我们总结了几点:

    1.大批量插入时候,用事务可以几何倍的减少插入时间,原因是会把sql都加入内存,然后一次性提交。

    2.针对经常变化的表、字段避免使用索引,当然索引带来的查询性能也是几何倍的增加,这里要说的一点是like并不会使用到索引

    3.巧妙使用sql语句增删改查,能够用一条语句解决的事就不要使用两条,比如之前我们发现一个设置已读消息,是把这个人的消息取出来,然后再设置进去,后来我们直接进行了这个人的update语句,发现这块的处理性能高了很多。

    4.当单条数据小于 20K 时,数据越小 SQLite 读取性能越高;单条数据大于 20K 时,直接写为文件速度会更快一些。

    5.ibireme.com提到过在官网下载源码编译的sqlite性能会高几倍,我确实编译了一下,但是编译出来的是dylib,现在的系统是tbd,我没转过去,并且pods里依赖的也是tbd的,所以也就作罢了,主要放弃原因还是发现我们将很多数据库操作的部分去掉了,cpu占用率也没有发生变动。如果有搞定的同学记得分享一下。

    数据库的大概能记住的也就这几点吧。

    大最后说一嘴,多用Instuments来检测你的方法执行时间,以及CPU GPU占用率,这能够很好的帮你优化你的程序。

    相关文章

      网友评论

        本文标题:iOS消息高并发优化

        本文链接:https://www.haomeiwen.com/subject/mxbwqftx.html