iOS 基于MVVM设计模式的微信朋友圈开发

2022年01月16日 阅读数:2
这篇文章主要向大家介绍iOS 基于MVVM设计模式的微信朋友圈开发,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

iOS 基于MVVM设计模式的微信朋友圈开发_控件


前言

  • 微信朋友圈一直以来都是iOS开发人员争相模仿的界面,主要是其包含了丰富的iOS所需知识点,以及经常使用的功能模块。固然各个功能模块实现过程当中的细节处理以及用户体验的优化,这才是咱们开发者在平常开发中须要关注和增强的地方。
  • 本文笔者将着重分析微信朋友圈实现的具体过程以及细节处理,争取把里面的全部知识点,模块虽小,但五脏俱全,其中最主要分析的是朋友圈的界面布局的细节处理以及性能优化。但愿为你们提供一点思路,少走一些弯路,填补一些细坑。文章仅供你们参考,如有不妥之处,还望不吝赐教,欢迎批评指正。
  • 微信朋友圈的基本架构是基于​​MVVM + RAC + ViewModel-Based Navigation​​来实现的。
  • 微信朋友圈的界面控件布局和富文本显示内容,主要是使用YYKit 来完成的,若对其不熟练的,请事先作好准备哦。

分析

前期在敲代码以前,须要着重分析一下整个微信朋友圈界面的实现方案,这多是本篇文章的核心所在了(PS:这里特别提醒一下广大开发者,在实现某一个功能前,请务必肯定一个实现方案,可能实现的方案千千万,这就须要开发者经过自身的理解来肯定一个最优的方案来实现,而不是一昧毫无头绪的敲代码,形成后期又得从新迭代的悲剧!!!)。微信朋友圈的效果图以下(PS:万恶的马赛克…)。php

iOS 基于MVVM设计模式的微信朋友圈开发_输入框_02固然总体的界面的布局仍是比较复杂的,前期看了UI仍是挺让人望而却步的。首先,咱们能够肯定的是总体是利用​​UITableView​​来实现的,是否是你们已经隐隐约约感觉到​​仍是原来的配方,仍是熟悉的味道​​,相同的​​tableView​​,变得只是​​Cell​​罢了。其次,笔者通过多日在​​GitHub​​上搜寻一些实现微信朋友圈的开发的​​Demo​​,以及作了大量的市场调研和内容对比,发现最具表明性的两个​​Demo​​分别是:https://github.com/gsdios/GSD_WeiXin和https://github.com/zhengwenming/WeChat,其余​​Demo​​大多数都是参考这两个​​Demo​​来作的,固然这两个​​Demo​​实现微信朋友圈的方法涉及到两个不一样的方案,笔者就带你们简单分析一下各自的方案实现过程以及目前存在的弊端(PS:这里所谓的弊端,只是针对微信朋友圈而言的)。二者的界面模块划分以下(PS: ​​① 红色框 , ②:绿色框​​):iOS 基于MVVM设计模式的微信朋友圈开发_输入框_03固然这两个​​Demo​​的实现朋友圈的 ​​共同之处​​就是:将图上所示的​​红框①​​总体用一个​​UITableViewCell​​来展现。​​不一样之处​​ 就是:图上所示的​​绿框②​​的控件选取不一样罢了。​​UITableViewCell​​上布局子控件相对于你们确定是小菜一碟,这里笔者就针对两个​​Demo​​在​​绿框②​​的控件的选取上作文章以及分析其目前存在的弊端。固然这两个方案目前都不是最最优化的方案,经过分析其中存在弊端,逐渐引伸出比较使人合理的方案,固然笔者最终会给出本身的方案,但也许未必是最优的方案,更好的方案或许就存在你们的手中,笔者这里主要强调的是 ​​知其然,知其因此然​​。话很少说,Let's Do It!ios


  • 方案一 【https://github.com/gsdios/GSD_WeiXin】
    该方案将​​绿框②​​的控件选取的是一个普通的​​UIView​​,固然内部显示文本(​​评论、回复、点赞​​)的子控件用的是​​UILabel​​来展现。虽然这种写起来比较通俗易懂,就是根据​​评论列表​​和​​点赞列表​​的内容,不断修改内部​​UILabel​​的​​frame​​来达到要求,可是却带来了以下的弊端:git

  • 布局复杂:**考虑到​​绿框②​​内部子控件的布局的复杂性,其做者采用的是其本身写的SDAutoLayout来实现,笔者对SDAutoLayout用的也不是很是熟练,关于其布局代码的实现请留意其​​Demo​​的​​SDTimeLineCellCommentView.h/m​​文件便可,尽管其内部布局代码看起来还算简单,可是若是咱们不使用SDAutoLayout,那么采用传统的​​frame​​布局,想一想仍是比较复杂的,好比:咱们要计算出​​红框①(UITableViewCell)​​的高度,首先须要计算出​​绿框②​​内部全部子控件(​​UILabel​​)的尺寸,从而推算出​​绿框②​​的总体高度,最终方能肯定​​红框①(UITableViewCell)​​的高度。笔者猜测该做者这里可能主要是为了凸显SDAutoLayout的自动布局的强大和便捷,好一个​​项庄舞剑,意在沛公​​呀。github

  • 动态建立:**咱们知道​​红框①(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​​里面装的子控件也会愈来愈多且保持只增不减的趋势,这样该方案就显得比较的无力了。数组


- (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];
}
}

以上就是【https://github.com/gsdios/GSD_WeiXin】目前笔者发现其存在的些许问题以及谈谈笔者我的的一些理解。固然这个方案在针对大量的评论数据的处理上或许稍有吃力,可是若是当评论列表的个数是固定,例如:优酷视频的评论回复(以下图)。这个方案也不失为一个好的解决方案。因此说业务场景不一样,实现方案不一样,可见在敲代码以前,先思考后肯定实现方案是多么重要。缓存

iOS 基于MVVM设计模式的微信朋友圈开发_控件_04方案二 【https://github.com/zhengwenming/WeChat zhengwenming/WeChat】性能优化


  • 该方案将​​绿框②​​的控件选取的是一个​​UITableView​​,也就是说​​Cell(红色框①)​​里面嵌套了一个​​UITableView​​,其内部子控件就是​​UITableViewCell​​来处理,后面的处理其实就跟咱们日常处理​​UITableView​​的方法同样,建立​​TableView​​,遵照协议,实现协议方法… ,可能会不习惯的就是日常建立的​​TableView​​,咱们都是将其添加在控制器的​​View​​上,这里只是添加在​​UITableViewCell​​上罢了,其余并没有差别。内部实现说到底其实就是充分利用​​UITableView​​的特性,选取不一样​​UITableViewCell​​来显示​​点赞列表​​和​​评论列表​​而已,相比于方案一来讲,该方案主要发挥出了​​UITableView​​的特性,经过实现​​UITableView​​的协议方法就能实现评论和点赞列表的展现,且实现起来更加简单易懂,这多是目前市场上绝大多数的作法。虽然外表看似毫无破绽,可是其中隐藏巨大弊端。以前笔者也利用这种方案,写过相似微信朋友圈的评论回复,可是其中存在的问题,笔者却没有叙述,实属抱歉,固然这里笔者将详述其存在弊端和产生的缘由,以及让你们从新加深对​​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:知识点有木有),固然你们能够跑跑笔者写的这篇文章:架构

  • 方案三 【https://github.com/CoderMikeHe/WeChat】
    该方案正是笔者目前使用的方案,该方案不只很好的解决了​​方案一​​和​​方案二​​目前存在的弊端,并且使用起来极其简单方便以及性能优化上更是前两个方案没法比拟的,固然最主要的仍是考察技巧性(​​黑魔法​​)。首先笔者在认定该方案以前,前期笔者是作了大量的准备工做,以及仔细琢磨了​​红色框①​​(PS:相似一条说说)这个总体的子模块组成。固然必须明确的是微信朋友圈的需求:​​绿色框②​​可以展现大量的评论数据(即:评论内容列表的个数>=100 ,虽然咱们会不多看到某我的的某条说说,有100多我的的评论内容,并且微信的朋友圈信息流动性很是快,这种大数据的产生会不多发生,可是这种大数据不表明没有)。①考虑到微信朋友圈这一个硬需求,笔者着重从性能上出发,第一想到的就是利用​​Cell的复用机制​​来展现每条说说的评论内容;②考虑到前两个方案都是把​​红色框①​​当作一个总体来处理,且都来了相似的弊端以及针对评论内容大数据所带来的性能问题,以避免重蹈覆辙,笔者将​​红色框①​​拆分为下图几个模块:一条说说(红色框①) = 组(段)头(绿色框②) + Cell(紫色框③) + 组(段)尾(黑色框④)


iOS 基于MVVM设计模式的微信朋友圈开发_微信朋友圈_05经过上图所示,虽然该方案在模块划分上是比较的分散,可是其整体带来的性能是很是客观的,大大保证了朋友圈列表滚动的流畅性。其中固然最最主要的缘由仍是归功于上图所示的​​组(段)头(绿色框②)、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:(NSIndexPath *)indexPath;

固然,​​组(段)头、组(段)尾​​的内部控件布局,想必对于你们已是手到擒来的东西,这里笔者也不过多讨论,详情请参考笔者提供的​​Demo​​,自行领会。这里笔者主要想说的就是​​Cell(紫色框③)​​,首先日常开发过程当中,​​Cell​​的宽度通常是跟所处的​​tableView​​的宽度是一致的,可是微信朋友圈里面的这个评论​​Cell​​明显不是,这里笔者须要强调的是:重写是个好东西。这里的关键点就是在于重写自定义的​​UITableViewCell​​的​​- (void)setFrame:(CGRect)frame​​方法,关键代码以下:

/// PS:重写cell的设置尺寸的方法, 这是评论View关键
- (void)setFrame:(CGRect)frame{
frame.origin.x = MHMomentContentLeftOrRightInset+MHMomentAvatarWH+MHMomentContentInnerMargin;
frame.size.width = MHMomentCommentViewWidth();
[super setFrame:frame];
}

固然对于这种方案(​​组(段)头+Cell+组(段)尾​​)的实现过程,笔者之前就写过一篇文章,来详细介绍这其中的关键点。最后,笔者我的认为这个方案目前是实现相似微信朋友圈这种支持无限评论需求的最优雅的实施方案。固然还有一种方案就是:微信官方团队作朋友圈开发的实现方案。若是这篇文章可以有幸被微信的开发人员看到,也请微信的开发人员分享一下微信官方的朋友圈的实现方案哦;或者若是笔者的这个方案正好和微信官方的一模一样,那么也请为笔者疯狂打​​Call​​(权威认证)哦。最后笔者但愿这篇文章可以为你们解除些许疑惑,带来些许帮助。

功能

分析开发笔者在微信朋友圈时多遇到一些比较须要技巧性的功能模块的实现以及细节处理,固然实现的方案必定不是惟一的,可是笔者的目的是但愿你们可以积极讨论,而后继续完善朋友圈的各个功能模块。

  • 评论/回复时,TableView
    滚动到指定的区域**
    这个功能是目前小伙伴问的最多的小功能模块之一,具体效果图以下所示(PS:打开本身手机中微信朋友圈,玩弄一下):首先咱们的明确该功能主要是为了不键盘
    和评论输入框
    遮盖住用户想要评论或回复的内容。其次微信朋友圈官方作法(需求)是:① 用户点击组(段)头(绿色框②)
    上的弹出的【评论】按钮 , 弹出键盘
    和评论输入框
    ,咱们须要保证组(段)尾(黑色框④)
    的底部显示在评论输入框
    的顶部,且伴随着评论输入框
    高度的变化而组(段)尾(黑色框④)
    的底部仍旧显示在评论输入框
    的顶部; ② 用户点击评论Cell(紫色框③)
    弹出键盘
    和评论输入框
    ,咱们须要保证评论Cell(紫色框③)
    的底部显示在评论输入框
    的顶部,且伴随着评论输入框
    高度的变化而评论Cell(紫色框③)
    的底部仍旧显示在评论输入框
    的顶部;

iOS 基于MVVM设计模式的微信朋友圈开发_输入框_06通过上述的需求分析,咱们能够明确的是:当弹出​​键盘​​和​​评论输入框​​时,滚动​​TableView​​到合适的区域来知足上述的条件便可,滚动​​TableView​​无非就是设置其​​contentOffset.y​​(PS:​​- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated​​)的值便可,目前最关键的就是计算出:须要计算出​​contentOffset.y​​的值便可。笔者仅以上面的​​需求①​​为例,来讲说笔者的思路和作法,首先若是弹出​​键盘​​和​​评论输入框​​时,​​TableView​​不设置合适​​contentOffset​​,则UI效果无非是如下三种状况:(PS:红色分割线表明​​键盘​​和​​评论输入框​​弹出时,​​评论输入框​​的顶部所处的位置 , 其余颜色的分割线表明​​组(段)尾(黑色框④)​​的底部位置)

场景一

场景二

场景三

iOS 基于MVVM设计模式的微信朋友圈开发_控件_07

iOS 基于MVVM设计模式的微信朋友圈开发_输入框_08

iOS 基于MVVM设计模式的微信朋友圈开发_微信朋友圈_09

delta = Y1 - Y2 < 0

delta = Y1 - Y2 = 0

delta = Y1 - Y2 > 0

场景一:该状况若是不设置​​TableView​​滚动,那么势必会致使​​键盘​​和​​评论输入框​​遮盖住评论内容(PS:蓝色分割线),从而影响用户体验;若是想要显示出评论内容,只须要让​​TableView​​向上(​​delta < 0​​)滚动​​abs(Delta)​​距离。场景二: 该状况属于理想状态,​​TableView​​无需上下滚动。场景三:该状况若不处理,则会致使要评论的内容,距离​​评论输入框​​很远,会让用户怀疑这条评论到底是评论谁的,从而影响用户体验。为了达到需求,则须要让​​TableView​​向下(​​delta > 0​​)滚动​​Delta​​距离便可。想必经过上表的画图分析,想必你们已经成竹在胸了吧。目前最主要的是:​如何计算出Delta = Y1 - Y2的值?​首先咱们应该知道,​​评论输入框​​和​​组(段)尾(黑色框④)​​处于不一样的坐标系,不能直接计算,因此,​关键点就是咱们必须将组(段)尾(黑色框④)的坐标系转换成和评论输入框同样的坐标系​,其次再设置​​TableView​​的​​contentOffset.y = contentOffset.y + delta​​便可。需求②也采用相似的方法来处理便可,这里笔者再也不赘述,详情见Demo,关键代码以下所示:

/// 评论的时候 滚动tableView
- (void)_scrollTheTableViewForComment{
CGRect rect = CGRectZero;
CGRect rect1 = CGRectZero;
if (self.selectedIndexPath.row == -1) {
/// 获取整个尾部section对应的尺寸 获取的rect是至关于tableView的尺寸
rect = [self.tableView rectForFooterInSection:self.selectedIndexPath.section];
/// 将尺寸转化到window的坐标系 (关键点)
rect1 = [self.tableView convertRect:rect toViewOrWindow:nil];
}else{
/// 回复
/// 获取整个尾部section对应的尺寸 获取的rect是至关于tableView的尺寸
rect = [self.tableView rectForRowAtIndexPath:self.selectedIndexPath];
/// 将尺寸转化到window的坐标系 (关键点)
rect1 = [self.tableView convertRect:rect toViewOrWindow:nil];
}
if (self.keyboardHeight > 0) { /// 键盘抬起 才容许滚动
/// 这个就是你须要滚动差值
CGFloat delta = self.commentToolView.mh_top - rect1.origin.y - rect1.size.height;
[self.tableView setContentOffset:CGPointMake(0, self.tableView.contentOffset.y-delta) animated:NO];
}
}
后记

本篇文章笔者主要是分析实现微信朋友圈的最优方案,但愿能为你们在敲代码以前树立一个正确的参考,这样可以避免你们走许多弯路。固然微信朋友圈的技术要点和技术细节,虽然看似简单,可是细节处理很是重要,笔者在接下来的时间内,会陆续为其增长更多功能模块,以及将在开发​​WeChat​​朋友圈中用到的好用技术以及细节处理分享出来,但愿提供你们一个参考,争取能为你们答疑解惑。固然也但愿你们踊跃发言,共同交流,共同进步。

期待



iOS 基于MVVM设计模式的微信朋友圈开发_控件_10