iOS架构补完计划

移动端架构上的设计,本质上都是三个角色:数据结构、数据流管理、数据展示。

不管是MVC、MVVM、MVP、VIPER或者任何新的设计模式、都跳不出这三个角色。无非是把数据管理者的工作进行拆分、唯一的界定标准就是把工作拆分的粒度大小。
而无论哪种思想、最终都逃不开三个问题的《《取舍》》。代码量、通用性、可读性。

MVC

MVC就是典型的着重通用型与可读性、这正是一个作为万物之初的架构所需要保证的事。简单、易学。

架构模型

  • Model:数据结构,数据映射
  • View:数据展示
  • Controller:负责根据需求对Model及View间的数据流调配

Controller的瘦身

不过和广义的MVC不同,iOS端由于UIViewController自带一个容器View。所以除了上述的正统任务之外,Controller还需要承担View的生成,布局等的任务。Controller实际上是Controller+View的一个角色,这样的设定也导致了C层过于臃肿的问题

  • 胖Model

    主旨是Controller从Model里拿到的数据、不需要进行更多的判断、处理等操作、就能使用。缺点是移植困难、复用困难

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Json Data:
    timestamp:1234567

    FatModel:
    @property (nonatomic, assign) CGFloat timestamp;
    - (NSString *)ymdDateString; // 2015-04-20 15:16
    - (NSString *)standardString; // 3分钟前、1小时前、一天前

    Controller:
    self.dateLabel.text = [FatModelIns ymdDateString];
    self.gapLabel.text = [FatModelIns standardString];
  • 瘦Model

    就是要把MVC的M贯彻倒底、除了业务的表达啥都不管。所以瘦Model要借助一些外来的辅助模块(索性可以叫Helper、Util)来对弱业务做抽象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    Json Data:
    {
    "name":"casa",
    "sex":"male",
    }
    SlimModel:
    @property (nonatomic, strong) NSString *name;
    @property (nonatomic, strong) NSString *sex;
    Util:
    #define Male 1;
    #define Female 0;
    + (BOOL)sexWithString:(NSString *)sex;
    Controller:
    if ([Helper sexWithString:SlimModel.sex] == Male) {
    ...
    }

总结:虽然胖瘦Model都一定程度上尝试解决Controller过于臃肿的问题,但是仅仅停留在数据处理、格式化层面,真正的业务逻辑、交互逻辑依然存放于Controller中

MVP

本质上与MVVM没有区别,只是将Controller中关于Model和数据处理的代码移植到Presenter中,但是MVP并不是一个完全剥离业务逻辑的设计模式

架构模型

  • View

    负责界面结构和布局管理,通过Presenter暴露的数据更新和数据获取的接口进行展示

  • Presenter

    负责处理数据,并提供刷新反馈接口,并管理Model。

  • Model

    和MVC中的一样,提供数据模型和映射

MVVM

MVVM起源于前端,这种抽离模型响应式架构在Web开发及小程序开发中已经十分普及,在大厂甚至成为一种标准存在。MVVM的出现,为移动端开发Controller/Activity的瘦身带来启发,刚才讲到的胖Model只从Controller中移植了一些简单的弱业务,而大量的交互逻辑仍存放在Controller中

MVVM力求解决的开发痛点

  • 过长的数据响应链,每次数据更新都要向上层传递
  • 控制器层代码臃肿。由于UIKit框架的设计原因,UIViewController承担了过多不应承担的责任,Controller内包含了数据、界面、逻辑元素,只能通过规范约束,难以管理
  • 架构层面结构划分不清晰,增加了移动端的学习难度

架构模型

  • Model

    和MVC中的瘦Model一样。只承载最基本的数据单元和数据映射。

    1
    2
    3
    4
    5
    6
    @interface UserModel: NSObject

    @property (nonatomic, copy) NSString *userName;
    @property (nonatomic, copy) NSString *sex;

    @end
  • View

    View层包含了UIKit框架下,UIView和UIViewController及其子类。在View层中只需要编写页面展示模板和数据绑定,不写任何业务逻辑。

    1
    2
    3
    4
    5
    - (void)awakeFromNib {
    [super awakeFromNib];
    RAC(self.userNameLabel, text) = RACObserve(self, viewModel.userName);
    RAC(self.userSexLabel, text) = RACObserve(self, viewModel.sex);
    }
  • ViewModel

    ViewModel里涵盖了所有View层绑定需要的数据模型、逻辑函数、事件响应函数。当数据发生改变,View层会响应式渲染更新界面。

RAC对于MVVM的意义

RAC(ReactiveCocoa)并不是MVVM思想的核心,不用RAC也能使用MVVM。但是使用RAC能更好的体现MVVM的精髓,即数据绑定处理、响应式渲染。就像Web开发中,使用React、Vue那样。在MVC中,M和V若无持有关系,在数据传递和渲染时会造成很多麻烦,那么如何把原本松散的两者通过中间层联系起来(数据绑定),在iOS本身并没有太靠谱的办法,在Vue中有这种优雅的写法。

虽然KVO、Notification、block、delegate和target-action都可以用来做数据通信进而实现绑定,但都不如RAC来的《《优雅》》。

关于架构设计的一些观点

  • 控制好Controller的代码量

    随着项目的进行、代码量最多只能优化、膨胀不可避免。而在没办法继续精简的前提下、想控制Controller的代码量。就要在可读性和通用性之间进行取舍。该挪走的时候就挪走吧、毕竟梳理一个单独的模块、比梳理一个几千行的Controller要方便多了。

  • 对于MVX如何选择

    • 其实完全要看业务性质以及项目复杂度
    • 如果你一个页面只有一个UITableView,搞出一些奇淫技巧其实意义不大,徒增烦恼。踏踏实实用MVC对大家都好。
    • 如果业务中View存在多种样式风格,底层数据处理存在大量复用的情况,可以使用MVP
    • 如果业务相当的复杂,耦合让人浑身难受。做好模块化或者干脆VIPER才是出路。
    • 如果你追求如前端MVVM框架(Vue、React)一般的开发体验,可以尝试使用RAC,但不建议单独脱离RAC使用MVVM架构
  • 无论用哪种模式、都要深刻的理解每个模块不同的职责

ps: 本文基于CocoaChina博主kirito_song的文章,并整理了近期项目实践撰写。