
这是我们讨论iOS的Reddit应用程序体系结构的第一篇文章。 在这里,我们谈论的是更接近UI的功能。 特别是向
模型视图呈现器 (MVP)体系结构的过渡。 这种重构的优点:
- 提高代码的灵活性,清晰度和可维护性,以支持未来的增长并加快迭代速度。
- 滚动性能提高了1.58倍。
- 刺激单元测试。 测试数量从几个增加到200多个。
下面是我们分层体系结构的直观图。 第一篇文章将重点介绍“视图”和“演示者”级别。
我们分层架构的终极视角变更的先决条件
一年多以前,我们发表了文章
“在Reddit iOS App中构建功能区” 。 它讨论了如何以不低于99.95%的会话率生成高效,可扩展的Feed。 我们介绍了如何使用“模型-视图-控制器”(MVC)架构以及如何创建用于分页数据的抽象。
现在,Reddit已经成长为一个组织和服务,并且还在继续发展。 因此,对Reddit iOS应用程序的需求增加了。 它应该支持更多的功能请求,更快的迭代循环和更高的质量标准。 开发团队已从三人增加到二十多人。 原始的MVC架构几乎无法满足这些要求,因此必须进行架构更改。
问题的实质
随着时间的流逝,代码失去了灵活性和清晰度。 在iOS开发人员社区中,通常将缩写MVC称为Massive View Controller,因为视图控制器通常会膨胀成具有一千行以上的神圣对象。 尽管我们竭尽全力,但问题确实出现了:继承层次变得令人不适,并且控制者开始变成模糊的神圣对象,无法抵抗变化。
当我们决定更改磁带的显示级别时,我们将最后一根钉钉入了MVC棺材。 Reddit应用程序的用户在增长,因此滚动性能经常从60 FPS降到45-55 FPS。 这意味着您需要在保持原始实现的同时重写磁带表示层。 但是在现有的MVC体系结构中,如果不复制数千行代码,就无法重写磁带表示层。
另外,代码库的许多部分都很难测试。 代码在表示层的经过严格测试的类中,而依赖项通常是单独的(单个)或在类本身中进行了硬编码。 我们想了解正常测试的可能性。
为重构设置的其他任务:故障数量应保持较低水平,新架构应为将来的增长奠定基础,并且不干扰依赖于现有基础架构的功能。 也就是说,需要进化而不是革命性的改变。
过渡到MVP
我们决定为解决上述问题,需要新版本的应用程序。 在考虑了几种选择之后,我们决定使用
Model-View-Presenter (MVP)体系结构。 MVP符合所有这些条件,并且是众所周知且有文件记录的架构,因此培训工程师更加容易。 它还保留了“视图模型”的概念。 如有必要,您可以在Presenter中
根据唯一责任的原则创建视图模型对象
,并使用它们来扩展我们的视图。
模型视图演示者图表摆脱Massive View Controller
对于iOS应用程序,假定视图对象是UIView的子类,控制器对象是UIViewController的子类,而模型对象是简单的ol对象。 顾名思义,UIViewController将视图和控制器组合在一个对象中。 也就是说,由于表示层和控制器之间的紧密联系,iOS上的MVC模型经常失去其优势。 有趣的是,苹果公司本身也
意识到了这种联系 。
通常,iOS的Model-View-Controller架构会变成在MVP架构中,我们将这个概念考虑在内并进行形式化,将UIViewController真正视为表示层的显式对象。 近年来,将UIViewController视为具有不良名称的表示对象的概念已
变得流行 。
因此,我们删除了UIViewControllers中的所有无关逻辑。 然后,我们将Presenter分配给视图和模型之间的中介。 在这个新角色中,他不知道UIViewController之类的视图对象。 请注意,Presenter通过界面与视图进行交互。 从理论上讲,您可以将视图的实现更改为NSViewController(对于MacOS),等等。
通过引入Presenter和分担责任,我们使ViewController更容易思考MVP选项
如您在MVP图表中所见,该体系结构与MVC非常相似。 确实,相似之处多于差异。 这种架构仅有助于建立MVC寻求
的表示代码和业务逻辑的
正确分离 。 实际上,MV(x)体系结构的所有派生产品,例如MVP,MVVM,MVAdapter等,都只是同一概念的不同版本。
有人会问为什么我们完全放弃了MVC。 实际上,苹果公司描述了
不同类型的控制器 :用于模型,中介和协调。 老实说,也许我们可以用另一个控制器代替Presenter。 但是他们决定不这样做,因为大多数iOS开发人员出于某种原因都认为UIViewController是控制器的代名词。 使用Presenter一词,我们可以发出信号,表明该对象与具有某些功能和特性的常规控制器明显不同。
提高灵活性,可持续性和清晰度
“优先于继承而不是继承”是对象编程中的众所周知的口头禅。 对于继承,您需要预测未来并建立巨大的对象分类法。 但是,如果您的“理想”构建的继承体系由于不可预见的变化而开始瓦解,则很难修改这种僵化的结构。 在合成中,从其他对象创建对象并将工作委托给它们。 这很有用,因为只需更改对象组成的对象即可在运行时轻松更改其行为。 这些复合对象甚至更易于理解,因为代码被强制从继承层次结构中移出,从而面向一个特定任务的抽象。
这种组合性是MVP架构赋予我们的主要优势之一。 现在,您只需更改特定Presenter的组成即可更改控制器的行为。 现在,我们不再担心解密复杂而僵化的继承结构。 最后,视图控制器和Presenter对象更易于理解,因为它们具有一组更清晰的任务。
通过介绍Presenter并在其中移动视图控制器逻辑的一部分,我们简化了控制器继承层次结构。 下图显示,由于我们将所有这些逻辑都放入了Presenter中,因此我们能够删除GalleryFeedViewController类。 正如已经讨论过的,这样的继承层次结构更容易理解,也不太僵化。
通过组合简化继承的层次结构自由更改表示层的实现
如前所述,磁带滚动性能开始从60 FPS下降到45-55 FPS。 因此,对于磁带表示层,我们决定使用
Texture 。 它是基于Apple UIKit的开源平台,可通过在后台线程中进行预处理来提高界面性能。 在以前的MVC架构中,如果没有大量代码重复,我们就无法更改表示层的实现。
在实施MVP之前,您必须在ViewController中复制与View不相关的无关代码(橙色)新的MVP架构允许引入Texture支持,而不是从头开始重写。 我们只是将所有非View逻辑放在通用Presenter类中。 然后,他们编写了c纹理表示层的新实现,并重用了Presenter代码。 这为View的两种实现提供了支持,直到该为所有用户舒适地推出带有Texture的磁带了。
MVP实施后:非视图代码已移至共享演示者结果如何? 下图显示了磁带滚动性能的提高。 我们希望保持60 FPS左右,以实现绝对平滑的滚动。
单元测试
当然,我们不仅由于MVP实施了单元测试,而且这是一个重要因素。 特别是,MVP体系结构通过将代码移至易于验证的级别来扩展了测试领域。 副作用是视图级别变得更简单-因此,它们的测试频率较低。
将非View代码移到该层之外后,增加测试区域单元测试改善了对代码库的支持:它们使您可以更自信地进行更改,并有助于了解正确的行为。 它们还使代码更加灵活和易于理解,因为它们鼓励诸如依赖注入,组合和抽象编程之类的方法。 单元测试的数量已经从几个增加到200多个。
Reddit中MVP的批判性分析
尽管切换到MVP起到了很大作用,但仍然需要考虑一些事项。
磁带到纹理的过渡导致线程出现新问题。 该应用程序最初不支持View的异步实现。 也就是说,如果View状态与应用程序状态不匹配,则不可避免地会出现错误。 例如,磁带视图可能具有N条记录。 并且在后台线程中,应用程序的状态已悄然更改-现在包含少于N条消息。 如果差异没有得到解决,则当View尝试在Feed中显示第N个帖子时,应用程序只会崩溃。
最难修复的线程错误。 它们很难复制,因此很难调试。 我不得不更改请求的逻辑并接收数据以查看提要。 特别是,我们实施了“保护”,禁止在磁带的“视图”发生某些更改时对数据源进行任何更改。 此修复程序和其他较小的修复程序减少了与流处理相关的错误数量。 但是,仍然可以改进异步多线程。
其次,Presenter层在管道中代表了一个额外的“步骤”。 此步骤的代价是增加代码复杂度并降低性能。 有时,您只是想随便在UIViewController中执行此逻辑,或者是因为您习惯这样做。 在最坏的情况下,您会发现Presenter只是作为一个实体存在,没有任何有意义的逻辑。 在这种情况下,Presenter似乎无法证明其存在。
有时您可以在没有Presenter的情况下从View层切换到RedditCore层实际上,我们的应用程序尚未完全转换为MVP架构。 首先,将每个单独的UIViewController转换为Presenter将非常耗时-而不是演进。 其次,如前一段所述,有时根本不需要Presenter。 正如我们在为Ribbon实现纹理的工作中发现的那样,Presenter非常适合于简化大型MVC,或者用于实现具有可变行为的View,或者您需要检查复杂的逻辑。 但是有时UIViewController如此简单,以至于Presenter毫无意义。 因此它是可选的。 Presenter仅在必要时实施。
摘要和未来计划
在Reddit iOS应用中重构MVP架构有助于解决许多任务。 通过介绍Presenter层,我们逐渐开发了应用程序体系结构,以支持表示层的新实现,而不会破坏其他功能。 通过促进“大规模MVC”,代码变得更加清晰-将无关的逻辑转移到Presenter层。 我们还使开发人员能够更快地进行迭代并部署新功能。 并大大改善了测试。
鉴于所有这些,还有很长的路要走。 我们将继续创建Presenter对象并对其进行改进。 我们需要继续将无关的逻辑从UIViewControllers移到Presenter级别。 还必须使所有演示者都更好地遵守唯一责任原则。 最后,应用程序和体系结构都在不断发展。