Glow 技术博客 Service Oriented 的 iOS 应用架构 -- 第 21 课
欢迎浏览我们的技术博客tech.glowing.com,查看更多精彩内容。...
Intro
前不久我们上线了一款新的 App - Glow Baby,App 针对 0 - 12 个月大的新生宝宝,提供爸爸妈妈全面、健康、科学的育儿知识,帮助记录宝宝成长的点点滴滴。在 Glow Baby 的开发中,我们也做了一些新的尝试 - 使用 Swift 开发,并基于 Swift 的语言特点设计了新的 iOS App 架构。除了 Community 这部分的代码是作为一个私有的 Repo 引入,Glow Baby 基本是 100% Swift 代码。Glow Baby iOS 团队都是第一次接触 Swift,过程中我们踩过很多坑,遇到过很多抓狂的问题。但总体上,写 Swift 更加有趣,所有的努力最终也证明是值得的:App 运行更加流畅,代码更整洁可读性更高,我们开发效率也大大提高。
Baby App 跟 Glow 的其他几个 App 都是较为复杂的 App,因为像日记记录、本地存储、网络请求,服务器端数据的同步这些技术难点都有涉及。这些问题也都要求多线程编程。特别是数据同步,如何增量记录数据的增删改,什么时机跟服务器端进行同步。解决这些技术难题是非常有意思的工作,也是架构设计的创造性和乐趣所在。
接下来的一系列文章我们来看看 Baby App iOS 的应用架构。有些设计是基于 Swift 的语言特点的考虑,但并不妨碍整体的架构思路被应用在 Objective-C,甚至 Android 的 App 上。
MV(X)
在介绍 Glow Baby 的应用架构之前,先来看看目前 iOS 上最基础的架构 MVC,以及为解决 MVC 的毛病而诞生的其他几个架构,如 MVVM、VIPER 等。Cocoa 的很多技术跟架构都是基于 MVC。而且无论是文档、示例代码,还是创建一个项目时提供的模板代码,Apple 都鼓励开发者去使用 MVC。MVC 定义了 App 里对象的角色(Model-View-Controller),以及他们之间的交互方式:
- Model: 表示业务数据对象
- View:展现数据的 UI
- Controller:Model 跟 View 之间的粘合剂。一方面对 View 上的行为作出反应,通常会涉及到 Model 的更改;另一方面将 Model 的改动反映到 View 上
你可能听过 MVC 也被简称为 Massive View Controller,这就是原因所在 - View Controller 承担的职责太多:
- 网络请求
- 数据访问和存储
- UI 的调整和组合
- 业务逻辑
- View 的 delegate、data source
- 状态的维护
为解决 Massive View Controller 的问题,MVVM、VIPER 等架构应运而生。这里不再详细介绍这些架构,有兴趣的读者可以自行去 Google。
Baby App 没有使用 MVVM 和 VIPER。因为:
- 不够直观,提高了整体代码的复杂度,对于新入职的员工有一定学习成本
- 要发挥 MVVM 的优势,需要有 Reactive。Reactive 增加学习成本的同时,也让调试变得更困难。
- VIPER 虽然能平衡责任的分配,但由于引入过多对象,维护成本高。一个简单的页面也要求新增多个类和大量傻瓜代码
Service Oriented Architecture
面向服务的架构在服务器端的开发中很常见,它把业务分成了多个逻辑独立的组件。一个组件相当于一个 Service,封装了与其业务相关的功能,如 UserService 负责用户的注册、登入等,而 BabyService 有 Baby 的增加、移除、以及数据的记录等。Glow 服务器端的架构实际就是面向服务的。在 Baby App iOS 架构中引入 Service 的概念,是 App 开发过程中迭代的结果,灵感也是来自我们服务器端的架构。可以看到,Service 是对整个架构纵向逻辑切分的结果。抛开业务逻辑谈 Service 意义不大,Service 通常与数据库表的设计紧密相关。
横向的逻辑切分将 Baby App iOS 的架构自上而下切分成三个层(Layer):
- 应用层(Application Layer)
- 服务层(Service Layer)
- 数据层(Data Access Layer)
服务层和数据层把复杂的逻辑封装起来,作为 Framework 提供接口给上层调用。应用层只能调用服务层暴露出来的接口,而不能直接调用数据层。层次结构加强了可重用性和可测试性。应用层调用服务层提供的简单接口获得数据或者实行用户操作。服务层也不需要知道数据层中网络请求,服务器同步,以及数据持久化的具体实现。服务层,数据层,以及应用层都能很容易实现各自的单元测试(Unit Test)。
Framework 是很棒的工具。把服务层和数据层打包成 Framework,不仅帮助构建解耦可重用的代码,同时 App 的结构和业务逻辑也更加清晰。
应用层(Application Layer)
应用层也可以叫展示层(Presentation Layer),负责 UI 跟 展示逻辑。从 Code 角度说,就是 UIView 跟 UIViewController 的集合。复杂的逻辑都封装到了下层,UIViewController 就变得十分轻量。在 Glow Baby 中,一个 View Controller 通常 200 至 300 行代码之间,主要负责三件事:- 从 Service 获得数据(ViewModel)并展示
- 响应用户操作,调用相应的 Service 接口
- 监听 Service 层发出的消息,并执行相应操作,如更新 UI
ViewModel实例并不是
NSManagedObject或者其他持久化的 model 实例,跟 MVVM 中的 ViewModel 也不一样。在 Baby App 中,它只是简单的 Swift Struct,提供应用层需要的数据值。使用
Struct的好处主要是:
- 值类型(Value Type): 简单、容易理解,线程安全
- 松耦合的 View Controller,减少 View Controller 之间可能的交互
- 减少了 Statefulness 和 Mutability
- 更高效、占用更少内存
Baby App 支持 Theme,因此 Baby App 的 View Controller 还会调用 Theme 对象来设定 UI 的样式。但对 View 样式的设定都封装在了 Theme 里,所以并没有增加太多代码量及 View Controller 的复杂度。
服务层(Service Layer)
服务层定义了一系列 Service 和供给应用层使用的 ViewModel。Service 封装了 App 主要的业务逻辑,负责把底层持久化的 Model 和网络请求返回的 JSON 转换为 ViewModel,再提供给应用层使用。这样的分离即加强了 Immutablility 和 Statelessness,也让应用层中的 ViewController 更轻量,只需几行 Service calls。Service 虽然承担大部分业务逻辑,但一个 Service 通常也就 300 行左右的代码量,这得益于数据层的封装和抽象。数据层(Data Access Layer)
数据层的作用是提供简化的数据访问接口,主要有 3 个模块:- 数据存储(Persistence)
- 网络请求(Network)
- 数据同步(Data Synchronization)
Conclusion
在 iOS 上,MVC 因 Controller 的臃肿而遭到众人诟病。但其实 MVC 作为最基础的设计模式,展现了一个架构的精髓 - 抽象分离。这是我们应该学习思考的,而不是盲目从其他架构模式中选择一个来代替 MVC。Glow Baby iOS 的架构从可以看作是一种 MVC。从整体看,数据层是 Model,服务层是 Controller,应用层是 View。而如果看细节的地方,应用层跟服务层提供的 ViewModel 也可以看做一个 MVC:ViewModel - UIViewController - UIView.设计架构也没有「最好」或者「最正确」的方式,设计本身就是一项极具创造力的工作。但架构是有好坏区别,一个好的架构应该是对团队成员最为直观,同时扩展性良好的架构。
开篇先简单介绍下架构的整体,后续文章会具体分析各个层次的实现细节。
关注 CocoaChina
微信扫一扫关注公众号