思路1:7种不同角色
-
xxxViewController
处于最顶级,作为调用者 -
xxxView
和xxxViewModel
作为xxxViewController
的属性。一个负责界面显示,一个负责具体业务。xxxViewModel
的主要作用是给xxxViewController
减负,全面接管他的工作。这一步是MVVM
思想的最大体现,看似最简单,其实最关键。以后凡事找xxxViewModel
,跟xxxViewController
基本上没啥关系了。(数据、业务和界面解耦了) -
xxxUseCase
作为xxxViewModel
属性,完成xxxViewModel
的具体工作,比如网络请求,相关业务逻辑等等。 -
xxxBuilder
作为xxxUseCase
的属性。主要作用是将数据xxxModel
转换为适合xxxView
显示的数据xxxViewBean
。一般来讲,xxxModel
可以作为xxxBuilder
的输入参数,xxxViewBean
作为xxxBuilder
的返回值。 -
xxxUseCase
通过xxxBuilder
得到相应的xxxViewBean
之后,需要更新xxxView
的显示。这里的方法是通过xxxViewModel
,将weak
修饰的xxxViewController
传过来(防止引用循环),通过他,访问相应的xxxView
,利用新得到的xxxViewBean
进行更新。 -
xxxViewController、xxxView、xxxViewModel、xxxUseCase、xxxBuilder、xxxModel、xxxViewBean
有7种不同的角色。 - 其他地方说的
ViewModel
和这里说的ViewBean
有点类似。
思路2:4+1种不同角色
-
xxxViewController
处于最顶级,作为调用者,同时也是被“减负”的对象 -
xxxView
作为xxxViewController
的属性,负责界面显示 -
xxxViewModel
作为xxxView
更新界面相关方法的参数,其构造方法的参数是xxxModel
(可以为nil
,提供默认静态显示状态)。xxxViewModel
的作用是显示逻辑,对xxxModel
的一次适配。这一步很简单,不过确实这里两个关键点中的一个。将原先的一些琐碎的显示逻辑从xxxViewController
中转移过来。 -
yyyService
为xxxViewController
提供数据和业务服务。其服务对象是业务模块中的所有ViewController
,多个复用。等业务成熟之后,可以隔离为yyyService.framework
。调用接口,统一为类方法,具体实现是单例还是其他实例,对外部隐藏。 -
yyyService
服务的返回值是xxxModel
,他没有必要知道xxxViewModel
的任何信息。这一步也很关键,将数据和业务逻辑和界面、ViewController
解耦。 -
xxxViewController
的作用是一个“调用者”。调用yyyService
得到xxxModel
。调用xxxView
更新界面。xxxView
通过xxxModel
得到相应的xxxViewModel
,更新显示。 -
xxxViewController、xxxView、xxxViewModel、xxxModel
有4种不同角色。再加上多个共用的yyyService
,一共5种不同角色。 - 双向绑定,
swift
只要用属性观察器就可以了;Object-C
可以采用第三方库ReactiveCocoa
隔离出一个逻辑层
- 对于业务逻辑,采用
zzzLogic
的命名方式,作为第二层的抽象。这样就分为界面,逻辑,服务三层。层与层之间用协议进行沟通,通过framework
进行隔离。 - 推荐用
Swift
开发,一些好的特性不可获取。至于runtime,c++,c
等,可以在一个framework
中用Object-C
作为胶水过渡一下 - 在开发界面的时候,以
ViewModel
和逻辑协议与逻辑层沟通。协议定好之后,马上提供默认实现。这样整个过程就能独自前进。 - 逻辑层实现界面要求的协议,不能访问任何
UIKit
的对象,同时将一些跟具体业务逻辑没有关系的功能型问题下放给服务层,定好协议,马上提供默认实现。文件组织按照业务角度划分模块,不要考虑具体的页面。(不然的话,两套标准,必然混乱) - 服务层就负责实现逻辑层定好的协议。按照功能分模块,不要带入任何界面和业务逻辑的概念,只按照功能一个标准组织结构。(标准不统一,必然带来混乱)
- 在文件命名上,禁止使用
temp、core、common、public、base、tool
等抽象的名字,作为后缀可以考虑。 - 想
tableView
这种界面和数据强绑定的方式需要引入协议的方式,将界面和数据分开 - 像
SDWebImage
这种在UI上扩展数据功能的第3方库要换掉,或者改变使用方式。能干好下载一件事就可以了,不用瞎操心界面的显示 - 类别这种方式不要直接用,中间在包一层,以函数接口的方式对外提供服务。统一到类方法的包装之中,简单好用。
- 界面更新采用属性观察者模式,集中到观察
ViewModel
的变化上来,进行单向的数据绑定 - 反向通信以
block
的方式为主,一对一,关系简单,使用灵活 - 正向通信以函数返回值为主,多个值可以返回一个元组,很方便的
- 函数参数以
copy
形式的形参为主,改变实参的inout
参数尽量不要用 - 在必要的时候,可以用用
notification
,这个要注意通知都要求在主线程发送和接收,降低复杂度,减少不必要的麻烦 - 界面跳转采用
url
的形式统一编码,集中在一个文件中定义。具体的跳转实现在各自的controller
中,不要用统一的switch
结构 - 库管理工具推荐
Carthage
,当然cocoapods
也很好用,看习惯选择。 - 环境参数也放在一个统一的
plist
文件中,比如友盟的APPKey,baseURL
等 - 对于支付插件,分享等外接组件,统一用
framework
再包一层,对外提供自己定义的协议接口。不能让具体的功能实现污染统一的接口定义。 - 界面层有需要的时候也能够访问服务层。
- 访问顺序是单向的,不能反过来,比如服务层不能方位逻辑层和界面层;逻辑层也不能访问界面层。
- 在必要的时候,逻辑层或者服务层能够使用界面层传过来的
UI
对象,但是应该采用weak
引用,不能持有该UI
对象。UI
对象的生命周期由界面层独立负责。 - 真正的数据驱动应该在逻辑层,界面层只是简单的界面流转,不要带入数据和业务逻辑。就像故事版的连线一样,被动流转,不主动驱动。
备注
- 思路1已经在一些公司应用,xxxViewBean概念来自Java,相当于其他地方说的xxxViewModel
- 思路2还没有见到实际的应用,随着iOS8和动态framework逐渐被接受,可以考虑尝试一下。
- 本人偏向思路2,并且和动态framework结合起来用
- 总体感觉思路1过于繁琐,就像有名的VIPER一样。