草庐IT

iOS架构设计(三)- MVVM

erlich 2023-03-28 原文

我是不敢轻易谈MVVM架构设计的

终于在经过前面几篇文章内容的铺垫之后,现在简单说说自己的想法

切记,如果没有kvc kvo的原理知识铺垫,最好去复习一下,否则看过了解,回头就会忘却,不会形成意识

再次提醒:务必去了解一下 kvc kvo原理

因为我在文中MVVM的架构设计会依赖自定义KVO机制去实现

MVVM也会基于之前的 iOS架构设计(二)- MVP 去实现,所以没有看过的同学建议还是先出门阅读下

MVVM的本质

首先 架构设计更新到MVVM,是在MVC MVP KVC KVO的基础上发展而来的,我计划用前面的内容来成就MVVM,而不会增加新的内容,更不会平白无故的增加额外的管理类什么的

否则 前面几章铺陈就变得毫无意义可言 那这篇文章就如同行尸走肉一般 而我就是全身腐化气质了,我并不想那样

可能人各有偏好,我不理解的是 我看过一些别人写的MVVM,个人觉得没有特别适合我的,不知道是block炫技 还是秀delegate,思想我是get到了,可代码凌乱不堪,我考虑的是 我用这样的设计,怎么跟别人协作项目,我心里是没信息

争取我在此篇文章里阐述的能使我自己不迷糊,如能触碰到你心里的点,这篇文章的价值才出得来

先看一张图

image.png

这是我看到的一个利用MVVM的项目,我整理了下设计流程

图中的意思我简单概括下

  • 一个vc创造一个manager,manager呢功能很强,因为遵从3个协议,也就是把控制权交给了manager,但是这样的操作跟VC本身做又有什么区别

  • manager通过viewModel作为delegate 调度viewModel去管理view

  • viewModel又通过 manager遵从协议 驱动一些其他类,而且还是关联对象存储

  • viewMode又通过数据更新渲染view

  • 。。。。。

反正 我看到毛毛多的block,block泛滥,不是排斥,只是这种block爆发蔓延,看上去挺高大上,反正很快要抓住主干思想脉络 我是做不到的

我按照这样的架构设计去安排我的代码,图中的很多线我都需要考虑,不得不考虑,环节对不上,我的视图可能就刷新不起来;

而且很多关系是靠代码逻辑演化出来的,也就是我没办法从顶层设计上确切知道我未来的代码走势,复杂度会演化成什么样子,我都不知道

我自己没那个信心 能践行得很好,从项目角度上讲,我自己在这个架构设计下,是没有把控能力的

总之 我觉得是很乱,如果让我选,我毫不犹豫选择我之前设计的那个MVC,比这个好用,功能也不弱,协作起来问题也不大

回到正题

回到从MVVM最本质的思想

image.png

这张图的意思很明了,就两根线

  • 一根 View <-> viewModel

  • 一根 viewModel <-> Model

如果这篇文章里我们能解决这两根线,基本上就算实现了MVVM

除此之外,为了达到这个目的,我们的其他嫁接配置部分代码不应该侵入这两条线

这个思想很纯粹,很精辟 (ViewModel可以理解为一个大圣人,无所不能)

  • 视图通过 ViewModel来渲染 不管是主动的还是被动的,反正就一条线;

  • 视图的交互状态提交给 ViewModel;

  • Model通过ViewModel来交付;

  • Model本身的增删改 同步给ViewModel

接下来 我会按照上面的两条线的解读来设计

MVP的底子

image.png

会不会有人有疑问呀,这是骗人的吧?????

那么多代码,就这几条线,糊弄的吧。。。。

我相信会有人在心里这样犯嘀咕

因为很多工作包括配置是在基类初始化中完成的,所以我画了两条虚线,VC构建了两个重要的角色,View和Presenter

而这些基类跟配置属于万年不变的类,它的配置调试只属于架构者,项目定式后,可以选择作为库,项目中的其他成员是不涉及的,剩下的工作就是依赖那两条实线了

那对比一下上面MVVM的那个思想图

  • 我把最右侧的那条弯弯虚线变成实线

  • 给View添加观察者Adapter,

  • 给 Adapter添加观察者 Presenter,

这样就变成两条 双向的线,

而且我把Adapter换成一个神奇名字 -- ViewModel

Presenter可以换成 Model,不过从职能上来将,我还是觉得Presenter更合适

我就基本上把之前的MVP改造成了一个MVVM架构,

更确切的说,是 PVVM, P就是Presenter

这可能么,我倒不存什么疑问,最起码我是按照MVVM的设计思想来调整的我的MVP,不觉得有什么冲突的地方

从MVP处拷贝一份代码出来

image.png
image.png

首先这份mvp架构设计的代码是没问题的,只是我把关键字 list 替换成了 list1
接下来就是我们魔改了

adapter我打算沿用,名字不做更改,主要还是为了方便对比 MVP

给图方便描述修改过程,我把MVP原理图上的线给编上号

image.png

MVP调整过程

去掉线3

image.png

分两步

image.png

image.png

presenter驱动view刷新 变成了 adapter的责任

image.png

现在代码 按照图中 线的变化 调整,去掉线3,增加线6

由于线3去掉了,则View的渲染驱动就没有了,此消彼长,在别处也就是adapter补上

image.png
image.png
image.png

你会发现在adapter中 一句关键代码 [self needRefresh] View刷新渲染数据

目的是达到了,有点粗糙

可以先看下原因

image.png

其实就是View作为adapter的观察者,被动刷新渲染

image.png

MVP的架构设计只改了一点点,目前列表数据加载都是正常,加减操作 逻辑没有受任何影响

添加View -> Adapter 的一条实线

线5我画的是虚线,所以不用管它

image.png

不用改代码,线自然就补充上来了,就相当于 View触发 执行了一个同步流程,View也就拿到了返回值,刷新

这条线的添加就不用改代码了

顺便 Adapter -〉 Presenter 那条线也就有了

由此,MVVM也就改好了,我更习惯称之为 VAP, 也就是 View - Adapter - Presenter

Adapter 就是ViewModel Presenter就是Model

这种设计就是个双向的管道, 一头是View,一头是Presenter

Presenter是数据的源头,自然而然从Presenter流入View,期间经过Adapter

View最终是要把数据渲染到视图上去的,状态的变化自然要回溯到源头Presenter,中间也经过Adapter

比预想到修改要少好多好多,因为MVP中有一个context的设计,所以目前这种架构设计还可以再调整,
得益于灵活的context设计

你会否觉得与你预想的不一样,感觉文章就没说头,反正对我而言,设计就这些,更复杂的代码在此基础上 不会变得烦乱不可控,有凌乱的也只会是在局部,不会在整个设计中蔓延开来

我平时就是这样来控制的,不是说细节做到尽善尽美,也有瑕疵的地方,但不影响大局

还有一部分没拿出来说明,就是这个设计架构本身有些业务代码是来自工程编译本身产生的,

那是因为在这个设计里设定了一些规则,一些职能类的命名规范,还有功能流转方式是可以被这个架构设计严格限制的

这样即便项目庞杂了,想堆代码是不可行的,堆代码只会影响自己的业务本身,不安规矩来的话,就会发现代码根本是跑不通的

进一步抽象 改名

代码改到这个地步,感觉根一般见到的MVVM一点都不像,没关系,我们继续

抽象出一个baseview

image.png
  • 把订阅放到VC的配置阶段
image.png
  • View中去掉订阅逻辑,通用的订阅逻辑交由base
image.png

如此,View 与 ViewModel之间就从代码层面上看出订阅关系了

View与ViewModule的业务绑定

image.png

同样 这些也是base处理的,View本身透明

image.png

View订阅之后,剩下的就是

  • 数据的整体刷新到来,响应刷新

  • 根据订阅的自定义类型,局部处理

这里不选用block,是因为用block会在View里多了一些不必要的配置,不够纯粹,当前的方式 只需要处理这两个方法就可以了,而且换个模块 方法名也是固定的,基本上View里的代码个是就是固定的模板了

涉及的代码部分,同样可以从 github 获取

有关iOS架构设计(三)- MVVM的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  3. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  4. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  5. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  6. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  7. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  8. ruby-on-rails - 设计注册确认 - 2

    我在我的项目中有一个用户和一个管理员角色。我使用Devise创建了身份验证。在我的管理员角色中,我没有任何确认。在我的用户模型中,我有以下内容:devise:database_authenticatable,:confirmable,:recoverable,:rememberable,:trackable,:validatable,:timeoutable,:registerable#Setupaccessible(orprotected)attributesforyourmodelattr_accessible:email,:username,:prename,:surname,:

  9. ruby - 为什么不能使用类IO的实例方法noecho? - 2

    print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上

  10. ruby - Ruby 和 Ruby on Rails 中的三层架构 - 2

    我是一名决定学习Ruby和RubyonRails的ASP.NETMVC开发人员。我已经有所了解并在RoR上创建了一个网站。在ASP.NETMVC上开发,我一直使用三层架构:数据层、业务层和UI(或表示)层。尝试在RubyonRails应用程序中使用这种方法,我发现没有关于它的信息(或者也许我只是找不到它?)。也许有人可以建议我如何在RubyonRails上创建或使用三层架构?附言我使用ruby​​1.9.3和RubyonRails3.2.3。 最佳答案 我建议在制作RoR应用程序时遵循RubyonRails(RoR)风格。Rails

随机推荐