iOS架构模式杂谈

Quick Start:

假设有一个类微博列表,有一个时间label,显示类似刚刚、1天前这种。后端返回时间戳,对应Model字段pubTime.格式化后的结果,姑且叫pubTimeFormat.pubTime到fomrat转换逻辑的存放位置,决定了你的架构模式。

  • 如果放在model,即model新增一个pubTimeFormat字段,那就是胖model模式的MVC。
  • 如果新增一个类viewModel,viewModel暴露pubTimeFormat字段,pubTime是viewModel的私有变量,那就是MVVM.
  • 如果放在cell中,即类似self.label.text = [self formatTime:model.pubTime],那就是人们常批判的massiveVC架构模式.

iOS中的M-VC

针对上文中的第3点,有人可能会反驳,我写在cell内,跟VC有什么关系? 假设这个列表需要按天像日记一样分组显示,你会怎么做?在VC中遍历数组,判断时间,然后分组。这两者的本质其实是一样的,只不是一个是cell.contentView这种子view,一种是self.view这种父view,也就是开头说的,转换逻辑的存放位置,决定了你的架构模式.

在iOS中,view和viewControoler的界限并没有那么泾渭分明,每个VC自带一个容器view.再比如tableView的数据源方法中,我们经常写的cell.goodModel = self.models[indexPath.row]这种代码。从MVC的设计上来说,view是不应该知道model的细节的,但我们往往会在cell中引入Model头文件,进行各种配置。

如果一定要遵守view和Model隔离的原则,我们可能会在VC中写出

cell.userName = model.userName;
cell.publishTime = publishTimeFormat
复制代码

这种代码,view和model完全的隔离。但想必你会宁可选择之前的写法。

为解决这个疑问,让我们回到设计初衷来,为什么要隔离view和model呢?或者说不隔离会有什么问题?

Model的理解

model的定义应该是数据层,包括数据模型和相关数据的增删改查这类操作。Controller应该可以从model层直接获取数据,然后做数据的分发操作。

iOS开发中,model的概念很容易被简化为数据模型,网络请求->字典转模型->控制器持有数据源数组->模型赋值给cell,我们已经习惯了这种流程。

而如果Model不承担起数据层应有的职责,相关的代码自然被我们写到了Controller中,导致controller的臃肿。

而如果像开头说的,为满足不同页面的UI需求,我们可能会不断往Model中添加各种属性,也就是业界所说的胖Model.随之带来的问题就是model随着业务增长越来越爆炸,越来越难移植复用。

解答之前的疑问,隔离view和model,本质是要隔离view和数据层的增删改查操作。如果view能接触到数据层操作的细节,那么view就是一个全能的模块,它既对外接收用户的交互,又能自行操作数据层完成交互对应的数据更新。那这个view的复杂度可想而知.

ViewModel的理解

ViewModel是基于胖model的架构思路建立的,然后在胖model中拆分出Model和ViewModel.

关于MVVM,普遍说法是为Controller瘦身.其实准确来说,是胖model随着业务增长后带来了拆分的需求。而那种把转换代码写在Controller中,然后从Controoler中挪到ViewModel的,虽然是殊途同归,但这种演化路径显然是不科学的。

很多初次接触VM的,往往不知道VM里究竟该放哪些元素。其实viewAdapter可能更好的表达了viewModel的意图。它通过某些方式(网络请求、本地缓存)获取原始数据,根据视图的展示逻辑加工数据,并对外提供加工后的数据。

viewModel不是替代Controller的,他只承担展示逻辑,不承担视图操作逻辑。viewModel原则上不应该引入UIKit头文件,更不处理push、present等跳转逻辑。

关于绑定机制

iOS中可用的绑定机制没什么选择,面对陡峭的学习曲线,唯一的疑问就是要不要。我的答案是要。

如果说VM是给M或C减负,那么绑定机制其实是给程序员减负。

想象一下假如有一个类似网易云音乐的界面,有各种业务逻辑,取列表的第一首歌的封面做列表封面,有一个Label显示总歌曲数,有一个label滚动显示当前的歌曲等等等等。假如现在,我们侧滑删除一首歌。我们得手动触发相关视图的更新逻辑,调用各个模块的update方法,甚至有些update方法先后调用都必须遵守一定的顺序。这就考验我们程序员,在写代码的时候必须脑海中时刻牢记各种业务逻辑,防止漏更新。而一个后接手的程序员,该怎么下手。

引入绑定机制意味着什么,相当于我们单独开一个区域,所有的业务逻辑都在这里了,写完这个方法后,我们就可以释放脑海中业务相关的堆栈了。假如一个新程序员接手处理删除这个逻辑,他只需要关心操作数据本身,也就是类似[self.dataArr removeObjectAtIndex]。数据变化后,业务逻辑相关的视图就能随数据源的改变自动更新到位。

组合?继承?

在项目中,我们经常能见到baseViewController、baseView、baseModel等等这些基础类。关于这个话题,我们只需要问自己两个问题:1.是否有需要?2. 是否有更好的实现方式?

是否有需要,答案毫无疑问是yes.以baseViewController为例,里面往往会配置全局的背景颜色、隐藏显示系统导航栏样式、侧滑返回、各个页面生命周期的钩子等等。这些都是必要的配置,而且需要全局统一遵守。

回答第二个问题前,让我们先看一下继承存在什么弊端。假如有两个app,A和B.B中有我们自己写的模块,比如搜索模块。现在A也需要这个模块。假如是使用继承写的,我们面对的第一个问题就是拔出萝卜带出泥。BApp中的控制器都继承了BBaseViewController,也就是说和BApp这个运行环境高度耦合。第二个问题就是如何接入AApp的环境,A也有个ABaseViewController配置了必要的环境信息。

选择了继承就意味着选择了主动耦合环境,此时就要慎重考量这个环境。比如以下两种情况耦合环境是没有问题的。1.继承cocoa touch框架中的类;2.独立模块内继承模块内的类;这种情况下,环境是无感的。

理想的情况应该是:业务不需要通过继承即能够对ViewController进行统一配置。业务即使脱离环境,也能够独立运行;ViewController一旦放入环境,不需要添加额外的代码即能自动获取环境赋予的配置和能力。具体实现上,就是环境需要有主动拦截能力,拦截到后注入环境配置代码,也就是AOP。

AOP替代基类

以AOP代替baseViewController为例.首先我们需要一个UIViewController+nonBase的分类,包括各种原本写在基类里的方法和属性(关联对象).

第二步,我们需要替换类似[super method]的效果,让原本定义在基类里的配置代码可以默认生效。自然我们容易想到method swizzing + Category的方案。新建第二个类ViewControllerIntercepter拦截器,在load方法里对UIViewController的相关方法进行拦截,并执行需要默认生效的相关配置。

第三步:仅从功能的角度,前两步其实已经达到了我们要求。但在项目实践中,我们可能并不想这么彻底的侵入。比如FDFullScreenPopGestrue这个框架,就是用类似的思路实现了无感的全屏侧滑返回。但某些页面,可能不想要或不适合侧滑返回。我们希望具体的页面可以选择接入环境或拒绝接入。虽然这样会导致一些额外的代码,但兼顾了迁移时的风险可控性。具体实现时,我们可以定义viewControllerProtocal,拦截后判断被拦截对象有无遵守这个协议来判断是否执行环境配置。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值