思路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一样。