前言
微信朋友圈一直以来都是iOS开发人员争相模仿的界面,主要是其包含了丰富的iOS所需知识点,以及常用的功能模块。当然各个功能模块实现过程中的细节处理以及用户体验的优化,这才是我们开发者在日常开发中需要关注和加强的地方。
本文笔者将着重分析微信朋友圈实现的具体过程以及细节处理,争取把里面的所有知识点,模块虽小,但五脏俱全,其中最主要分析的是朋友圈的界面布局的细节处理以及性能优化。希望为大家提供一点思路,少走一些弯路,填补一些细坑。文章仅供大家参考,若有不妥之处,还望不吝赐教,欢迎批评指正。
微信朋友圈的基本架构是基于
MVVM + RAC + ViewModel-Based Navigation
来实现的,如若不懂,还请点击iOS 基于MVVM + RAC + ViewModel-Based Navigation的微信开发(一)。微信朋友圈的界面控件布局和富文本显示内容,主要是使用YYKit来完成的,若对其不熟练的,请事先做好准备哦。
分析
前期在敲代码之前,需要着重分析一下整个微信朋友圈界面的实现方案,这可能是本篇文章的核心所在了(PS:这里特别提醒一下广大开发者,在实现某一个功能前,请务必确定一个实现方案,可能实现的方案千千万,这就需要开发者通过自身的理解来确定一个最优的方案来实现,而不是一昧毫无头绪的敲代码,造成后期又得重新迭代的悲剧!!!)。微信朋友圈的效果图如下(PS:万恶的马赛克...)。
Moment.jpeg
当然整体的界面的布局还是比较复杂的,前期看了UI还是挺让人望而却步的。首先,我们可以确定的是整体是利用UITableView
来实现的,是不是大家已经隐隐约约感受到还是原来的配方,还是熟悉的味道
,相同的tableView
,变得只是Cell
罢了。其次,笔者经过多日在GitHub
上搜寻一些实现微信朋友圈的开发的Demo
,以及做了大量的市场调研和内容对比,发现最具代表性的两个Demo
分别是:gsdios/GSD_WeiXin和zhengwenming/WeChat,其他Demo
大多数都是参考这两个Demo
来做的,当然这两个Demo
实现微信朋友圈的方法涉及到两个不同的方案,笔者就带大家简单分析一下各自的方案实现过程以及目前存在的弊端(PS:这里所谓的弊端,只是针对微信朋友圈而言的)。两者的界面模块划分如下(PS: ① 红色框 , ②:绿色框
):
Moment_UI.jpeg
当然这两个Demo
的实现朋友圈的 共同之处
就是:将图上所示的红框①
整体用一个UITableViewCell
来展示。不同之处
就是:图上所示的绿框②
的控件选取不同罢了。UITableViewCell
上布局子控件相对于大家肯定是小菜一碟,这里笔者就针对两个Demo
在绿框②
的控件的选取上做文章以及分析其目前存在的弊端。当然这两个方案目前都不是最最优化的方案,通过分析其中存在弊端,逐渐引申出比较令人合理的方案,当然笔者最终会给出自己的方案,但也许未必是最优的方案,更好的方案或许就存在大家的手中,笔者这里主要强调的是 知其然,知其所以然
。话不多说,Let's Do It!
方案一 【gsdios/GSD_WeiXin】
该方案将绿框②
的控件选取的是一个普通的UIView
,当然内部显示文本(评论、回复、点赞
)的子控件用的是UILabel
来展示。虽然这种写起来比较通俗易懂,就是根据评论列表
和点赞列表
的内容,不断修改内部UILabel
的frame
来达到要求,但是却带来了如下的弊端:- (void)setCommentItemsArray:(NSArray *)commentItemsArray{ _commentItemsArray = commentItemsArray; long originalLabelsCount = self.commentLabelsArray.count; long needsToAddCount = commentItemsArray.count > originalLabelsCount ? (commentItemsArray.count - originalLabelsCount) : 0; for (int i = 0; i < needsToAddCount; i++) { MLLinkLabel *label = [MLLinkLabel new]; [self addSubview:label]; [self.commentLabelsArray addObject:label]; } }
以上就是【gsdios/GSD_WeiXin】目前笔者发现其存在的些许问题以及谈谈笔者个人的一些理解。当然这个方案在针对大量的评论数据的处理上或许稍有吃力,但是如果当评论列表的个数是固定,例如:优酷视频的评论回复(如下图)。这个方案也不失为一个好的解决方案。所以说业务场景不同,实现方案不同,可见在敲代码之前,先思考后确定实现方案是多么重要。
YouKu_UI.png
布局复杂:考虑到
绿框②
内部子控件的布局的复杂性,其作者采用的是其自己写的SDAutoLayout来实现,笔者对SDAutoLayout用的也不是非常熟练,关于其布局代码的实现请留意其Demo
的SDTimeLineCellCommentView.h/m
文件即可,尽管其内部布局代码看起来还算简单,但是如果我们不使用SDAutoLayout,那么采用传统的frame
布局,想想还是比较复杂的,比如:我们要计算出红框①(UITableViewCell)
的高度,首先需要计算出绿框②
内部所有子控件(UILabel
)的尺寸,从而推算出绿框②
的整体高度,最终方能确定红框①(UITableViewCell)
的高度。笔者猜想该作者这里可能主要是为了凸显SDAutoLayout的自动布局的强大和便捷,好一个项庄舞剑,意在沛公
呀。动态创建:我们知道
红框①(UITableViewCell)
是支持复用的,这是毋庸置疑的,但是我们知道每一条说说(红框①
)中包含的评论列表的个数是不一样且Cell
高度也会不一样。这样就会涉及到当用户滚动朋友圈列表且cell
复用的时候,绿框②
内部的子控件的个数也是动态的,可能增多,又可能减少,这样就造成了动态增加或删除绿框②
内部的子控件,想必大家都知道尽量不要在UITableViewCell
中动态创建子控件,这是比较耗性能的,常规的做法都是事先在- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier
一口气创建所有你要显示的控件,这样你只需要根据数据的属性来显示或隐藏某个子控件即可,这样就避免了动态创建子控件的场景。但是由于朋友圈列表的每条说说的评论列表个数是不能事先确定的,所以必然会存在绿框②
动态创建子控件的悲剧。不支持大数据:对于上面动态创建控件的问题,其实该作者在内部
SDTimeLineCellCommentView.h/m
也是做了优化处理的,其做法主要是将动态创建的子控件(UILabel
)装进一个数组(commentLabelsArray
)里面,这样可以减少一部分的动态创建子控件过程,但是还是会存在动态创建子控件,其主要逻辑就是根据你传进来的说说的评论列表的个数 (commentItemsArray.count
)与commentLabelsArray.count
比较罢了,如果前者小于等于后者,就不需要动态创建,只是对commentLabelsArray
中的子控件做显示和隐藏处理即可;反之如果前者大于后者,这需要动态创建(commentItemsArray.count - commentLabelsArray.count
)个子控件,然后又被加入到数组commentLabelsArray
里面的过程。关键代码如下所示:
首先微信朋友圈的评论列表的个数是支持大数据的(PS:笔者瞎猜的…),那就必须确保绿框②
能支持大数据的显示,显然随着评论列表的个数逐渐增多,以及UITableViewCell
的不断复用,则绿框②
的commentLabelsArray
里面装的子控件也会越来越多且保持只增不减的趋势,这样该方案就显得比较的无力了。
方案二 【zhengwenming/WeChat】
该方案将绿框②
的控件选取的是一个UITableView
,也就是说Cell(红色框①)
里面嵌套了一个UITableView
,其内部子控件就是UITableViewCell
来处理,后面的处理其实就跟我们平常处理UITableView
的方法一样,创建TableView
,遵守协议,实现协议方法… ,可能会不习惯的就是平常创建的TableView
,我们都是将其添加在控制器的View
上,这里只是添加在UITableViewCell
上罢了,其他并无差异。内部实现说到底其实就是充分利用UITableView
的特性,选取不同UITableViewCell
来显示点赞列表
和评论列表
而已,相比于方案一来说,该方案主要发挥出了UITableView
的特性,通过实现UITableView
的协议方法就能实现评论和点赞列表的展示,且实现起来更加简单易懂,这可能是目前市场上绝大多数的做法。虽然外表看似毫无破绽,但是其中隐藏巨大弊端。之前笔者也利用这种方案,写过类似微信朋友圈的评论回复,详情请参考:iOS 实现微信朋友圈评论回复功能(二),但是其中存在的问题,笔者却没有叙述,实属抱歉,当然这里笔者将详述其存在弊端和产生的原因,以及让大家重新加深对UITableView
的理解。弊端如下:
复用问题: 若想保证
UITableView
滚动流畅,纵享丝滑,就离不开UITableViewCell
的复用机制(PS:这个复用机制
想必大家应该已经滚瓜烂熟了,这里笔者就不在赘述),这也是UITableView
的核心所在。首先正常情况下,我们可以确定的是红色框①
这个UITableViewCell
是能够Cell复用
的,这个应该是毫无争议的。但是红色框①
内部嵌套的绿色框②
这个TableView
中,其内部显示评论数据的UITableViewCell
是否也是支持Cell的复用机制
呢???可能大家的第一印象就是觉得是能的。但是这里笔者强调的是绿色框②
中CommentCell
是不支持复用的!!!大家认为CommentCell
能够复用
的,都是认为其复用机制
完全跟红色框①(MommentCell)
的复用机制
一样,都是会随着用户滑动的朋友圈列表,MommentCell 和 CommentCell
离开都会完全离开屏幕,然后将完全离开屏幕的MommentCell 和 CommentCell
存入缓存池,等到要显示Cell
的时候又去缓存池根据reuseIdentifier
去取MommentCell 和 CommentCell
,如果取得到,就直接拿来用;如果取不到,就去创建等过程....,这里笔者只能说cell复用
的概念倒是背的的挺熟,但是Cell复用
的机制却不够理解。原因是:* 之所以红色框①
这个MomentCell
能够遵循Cell复用
的机制,是因为首先其所处在的UITableView
的尺寸大小是和屏幕尺寸大小一致,其次朋友圈列表能够滑动的前提就是保证该TableView
的内容高度大于TableView
的高度,即tableView.contentSize.height > tableView.frame.size.height
,需要强调的是:①Cell
能否产生复用
取决于所处的tableView
能否滚动,②并且Cell
能够随着列表滚动完全离开所处的TableView
的显示范围。结合这两点必要条件,很快可以推断出红色框①
这个UITableViewCell
是能够满足Cell复用
的条件的。接着我们带着这两个必要条件来分析一下绿色框②
这个TableView
,首先明确的是,该TableView
的高度是根据评论列表中每个评论内容(CommentCell
)的高度总和(PS:tableView.height = cell0.height+cell1.height+cell2.height ...),这样就导致了该tableView
的内容高度等于tableView
的尺寸高度,即(tableView.frame.size.height = tableView.contentSize.height
),所以评论列表是不会滚动的,这样就不满足条件①;其次,其tableView
内部的CommentCell
相对于所处的tableView
的显示区域是完全暴露的,根本不满足条件②,所以最终真相大白,水落石出了,是不是豁然开朗,心情舒畅。 当然这里笔者友情提醒广大开发者千万不要误认为,只要Cell
看不见就一定会产生复用
的误区,主要是要明确该Cell
相对于所处的TableView
的显示区域是否看不见。(PS:知识点有木有),当然大家可以跑跑笔者写的这篇文章:iOS 实现微信朋友圈评论回复功能(二)所提供的Demo
,来验证一下笔者的这一说法。最后,如果绿色框②
这个TableView
一旦失去了Cell
的复用机制
,用脚趾头想想也知道,那造成的后果务必会重蹈方案一
存在的三个弊端的悲剧,这里笔者就不再赘述了,且笔者个人认为整体性能还不如方案一
的。
方案三 【CoderMikeHe/WeChat】
该方案正是笔者目前使用的方案,该方案不仅很好的解决了方案一
和方案二
目前存在的弊端,而且使用起来极其简单方便以及性能优化上更是前两个方案无法比拟的,当然最主要的还是考察技巧性(黑魔法
)。首先笔者在认定该方案之前,前期笔者是做了大量的准备工作,以及仔细琢磨了红色框①
(PS:类似一条说说)这个整体的子模块组成。当然必须明确的是微信朋友圈的需求:绿色框②
能够展示大量的评论数据(即:评论内容列表的个数>=100 ,虽然我们会很少看到某个人的某条说说,有100多个人的评论内容,而且微信的朋友圈信息流动性非常快,这种大数据的产生会很少发生,但是这种大数据不代表没有)。①考虑到微信朋友圈这一个硬需求,笔者着重从性能上出发,第一想到的就是利用Cell的复用机制
来展示每条说说的评论内容;②考虑到前两个方案都是把红色框①
当做一个整体来处理,且都来了类似的弊端以及针对评论内容大数据所带来的性能问题,以免重蹈覆辙,笔者将红色框①
拆分为下图几个模块:一条说说(红色框①) = 组(段)头(绿色框②) + Cell(紫色框③) + 组(段)尾(黑色框④)。
Moment_Plan3_UI.jpeg
通过上图所示,虽然该方案在模块划分上是比较的分散,但是其总体带来的性能是非常客观的,大大保证了朋友圈列表滚动的流畅性。其中当然最最主要的原因还是归功于上图所示的组(段)头(绿色框②)、Cell(紫色框③)、组(段)尾(黑色框④)
这三个控件都是可以通过使用TableView
的数据源方法以及代理方法(代码如下)轻松实现View的复用机制
的,而且都是平常开发中常用的方法,这样前面两个方案所存在的弊端就迎刃而解了。
/// UITableViewDelegate/// 组(段)头- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section;/// 组(段)尾- (nullable UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section;/// UITableViewDataSource/// Cell- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexP
作者:CoderMikeHe
链接:https://www.jianshu.com/p/2f161f6a310f