总结时读到的一系列文章
iOS应用架构谈 开篇
我的iOS工程结构
iOS项目的目录结构-原创
iOS 项目的目录结构能看出你的开发经验
iOS项目的目录结构
分层
三层架构 / MVC模式
Cocoa Core Competencies > Model-View-Controller
Concepts in Objective-C Programming > Model-View-Controller
浅谈三层结构与MVC模式的区别
三层架构是一种软件抽象的层次结构,是对复杂软件的一种纵向切分,每一层次中完成同一类型的操作,以代码的功能作为依据来分割,以降低软件的复杂度,提高其可维护性。一般来说,层次之间是向下依赖的,下层代码未确定其接口前,上层代码是无法开发的,下层代码接口的变化将使上层的代码一起变化。
三层架构一般分别是数据访问层、逻辑业务层和展示层。数据访问层直接跟数据库交互,负责数据的管理,与上层业务逻辑层交流时,通过数据对应的实体模型(model 类)。逻辑业务层则根据业务需求再对数据实体进行加工处理。而展示层则是面对用户的,是加工过后的数据的外壳。展示层可能是 web 页面,也有可能是其他客户端,比如移动应用。服务端通过提供 API 接口,更多的承担的是数据访问层和逻辑业务层的角色。
而 MVC 则是针对展示层的一种复合设计模式。它通过数据在展示层的流动方向来将代码分离成各个层次模块。首先是 Model 层,它对接了获取数据的 API 接口,即服务器端的逻辑层,这里 Model 层区别于三层架构中数据载体的实体模型 model,Model 层通过网络请求或者从本地获取数据,之后再对数据进行相关业务逻辑和其他的公共处理(如转换为实体类),然后交给 Controller。其实如果是调用 API 网络请求获取数据时,主要的逻辑操作可能是在服务端完成的,本地
Model 层的作用有时候可能仅仅是转换数据类型。View 层的任务是单纯的渲染出界面展示数据,或提供界面改变展示方式(如动画)的接口供给 Controller 调用。所以 Controller 就成为了 Model 层和 View 层之间的桥梁,数据从 Model 层流向 Controller,Controller 层决定如何调用 Model 层来获取或修改数据,并最终让 View 层展示数据,即数据最终流向 View 层。用户通过界面和系统交互后的数据反馈给服务器或持久化存储在本地,则是上述流向的逆过程。所以,一般 Controller 应该是小巧,简单的,它主要的功能应该是负责数据与展示引擎之间的调度。MVC 模式中的三个层间其实并不存在明显的层次结构,没有明显的向下依赖关系,他们更像是横向的切分。
iOS 中的分层
作为客户端开发的 iOS 应用,苹果公司本身通过 SDK 就已经将 MVC 模式的思想植入其中。
Controller
通过 UIViewController 作为基类,实现了界面的控制器。
View
通过 .xib 或 .storyboard 文件和继承 UIView 基类的视图类来实现 View 层。
Model
一般应用对于数据的处理,主要分为两部分,网络请求和本地数据。网络请求就是把数据的逻辑处理放在了服务器端,然后通过提供 API 接口给客户端来进行数据交换。而本地数据的处理相当于是要在客户端本地做类似服务器端的数据维护的工作,当然客户端的复杂度级别要比服务端低很多。
所以在分模块时,我们首先应把代码职责分为网络请求模块和本地数据模块。网络请求模块内封装了请求 API 的代码。而本地数据模块可以考虑像服务器端一样采用三层架构思想,数据访问层 Dao 只负责数据的存取,向上层屏蔽掉具体的存储方式,对于 iOS 来说包括NSUserDefault、.plist 文件和数据库等,在逻辑业务层 Service 根据业务需求再对数据加工处理。
网络请求模块和本地数据模块相当于是 Model 层,它们负责了数据的逻辑处理。这两个模块返回的逻辑处理后的数据都应该是原始的类型,并不转换为实体类,这种把职责分离的设计使得 Controller 层的操作更灵活。可以根据需求实现不同的适配器,以用来转换数据,比如当不同的 Controller 从同一个 Model 拿到数据后,可以通过不同的适配器转换为最终需要提供给 View 层的数据格式,可能是实体类,也可能是其他数据结构。这种方式并没有使 Model 层的代码侵入到 Controller 层内,Controller 层仍只是负责调配,适配器其实也属于 Model 层的一部分,只是这样的实现提高了灵活性。Controller 层从 Model 层获取数据的方式就是,先拿到原始数据,再按照需要组装合适的适配器,最终输出提供给 View 层的数据。
目录结构划分
iOS 工程中没有严格的分包机制,一般通过 Group 的方式在工程中实现逻辑目录划分,方便代码的组织和管理,使工程结构清晰和易于理解。应该在项目目录中建立与 Group 对应的目录,使代码文件的划分更清晰。一种快捷的操作是,先在磁盘上创建对应的文件夹,再把文件夹拖进 Xcode 项目中对应的位置,并选择 Create groups for any added folders,这样就创建了对应目录的 Group。
根据上述对 MVC 模式的理解,以其为基础可细分为:
- Application,与整个应用相关的文件,包括 AppDelegate 文件,main.m,项目配置文件(Info.plist),自定义的配置文件(.plist),老项目中的预编译文件。
- Module,按功能模块划分,每一个模块内再根据 MVC 模式分出 Controller 和 View 目录。View目录中存放的就是自定义的 view 类和 .xib 或 .storyboard 文件。除了功能模块,还需要有一个 Base 模块,用来存放一些需要继承的 base 类以及一个 Common 模块,用来存放一些各模块共用的组件。实体类并不属于某一个界面,所以应该单独放在外层。如果是某一个 Controller 不使用实体类,而是需要定制的适配器,可以在模块内添加 Adaptor 目录,目录内实现 Controller 对应的适配器。
- Network,主要是对使用 AFNetworking 进行 API 请求的封装。
- Persistence,本地持久化数据的处理,如上所述分为 Dao 和 Service 两层
- Model,数据的实体模型类,描述系统中的一些角色和业务,同时可作为适配器类,提供与网络请求层或数据持久化层提供的原始数据相互转换的方法。
- Category,存储对现有系统类和自定义类的扩展。同一个类的扩展也尽量按处理的方向不同分为多个扩展来写,这样使模块粒度更小,使用更方便。
- Utility,系统常用工具类。同样应该按处理的方向不同尽量拆分为更小的模块。
- Constant,只有一对 Constant 文件,用来存放项目中一些公用的常量。使用某一个类时才会用到的常量不放在这个文件里,而是应放在对应类的文件中。
- Localizable,存放所有的用于国际化的 .strings 文件,一般主要分为各功能模块,common,network提示信息等几部分。
- Vendor,存放不支持 Cocoapods 的第三方类库。
- Resource,存放多媒体资源,非 .png 格式的图片文件。图片资源用 Asset Catalog 管理(ios开发-图片资源管理)
功能模块与 MVC
在主体功能代码与 MVC 模式划分的层级关系上,除了上述使用的外层以功能模块分组,每一个功能分组内,再划分为 View 层和 Controller 层的方式外,还有另一种是外层以 Model、View、Controller 划分,在 View 层和 Controller 层内再以功能模块对代码分组。选择先按照功能模块分组的原因是,这样的划分粒度更细,更便于组织管理。