首页 关于 微信公众号
欢迎关注我的微信公众号

iOS项目的目录结构

结论

不废话,我现在通常采用的目录结构如下:

|—MyProject
    |—ignore-folder // 放置不想同步到代码服务器上的内容,通常包括一些体积太大、经常变动、对项目运行影响不大的文件。需要在该目录下添加 .gitignore 对本目录做一些设置。
        |—readme.log // 因为 ignore-folder 目录下的内容都是不会同步到代码服务器上的,所以最好加一个 log 文件记录一下你在该目录的操作。
        |—3rdparty // 比如,一些不能用 CocoaPods 管理也不想同步到代码服务器上的第三方库。
        |—data // 比如,一些经常会变动的、自己的测试数据文件。
    |—Utility // 自己实现的一些通用性较好的功能代码,这些代码有比较好的接口且与本项目不存在耦合,可直接复用于其他项目。
    |—Common // 本项目的一些全局性代码,这些代码通常与本项目的业务逻辑存在一些耦合,所以不放在 Utility 目录中。
    |—Feature // 本项目的功能模块目录,该目录下将项目的功能划分为多个模块,每个模块穿透 MVC,可以独立划分出去。当然,在模块下你不采用 MVC,采用 MVVM 或其他架构方式也没问题的。
        |—Base // 定义本项目中各种 Controller、View、Model 的基础类或基础接口。
            |—Controller
            |—View
            |—Model
        |—Main
            |—Controller
            |—View
            |—Model
        |—User
            |—Controller
            |—View
            |—Model
    |—Resource // 本项目的资源目录,放置图片、音频等资料。
        |—Image
        |—Sound
|—Pods // 采用 CocoaPods 管理的第三方库。

目录结构的进化

形式一

|—MyProject
    |—ignore-folder
        |—readme.log
        |—3rdparty
        |—data 
    |—Utility
    |—Common
    |—Service
        |—LocalService // 封装在 DAO 层之上,直接对接业务逻辑层,提供本地数据服务。
            |—DAO // 封装本地数据库访问层的相关代码。
        |—WebService // 对接业务逻辑层,提供网络数据服务。
    |—Model // 封装项目中的实体类。
    |—View
    |—Controller
    |—Resource
        |—Image
        |—Sound
|—Pods

上面的目录结构主要特点是基于 MVC 的架构构建的。Model 层主要封装一些数据对象实体,它们的实例将在项目的数据流中流动;View 层主要放一些控件,被 Controller 层用来展示;Controller 层就是一些 ViewController,获得数据 Model,用 View 展示出来;Service 层则为 Controller 层提供本地或网络的数据服务。

以前跟同事一起开发的时候会分层来分工,比如,当对项目抽象完毕、设计好数据库后,就会由同学 A 负责映射数据库的 Model 层的开发,然后提供相应的 DAO 层、LocalService 层的接口,一般就是常见的增删查改操作接口,当负责 Controller 层的同学 B 需要本地数据时,则去调用同学 A 提供的 LocalService 层的接口。这时问题就出现了,由于同学 A 开发 LocalService 层的接口时,并没有接触到上层的业务逻辑,他设计接口时只能充分发挥他自己的想象力,一般也就是提供常见的增删查改操作,这些接口与业务逻辑对接时,常常会发现要么冗余,提供的接口根本就用不上,要么对于复杂点的业务逻辑支持不够,想复杂一点操作数据,现有的接口却支持不了。所以,就需要考虑换一种分工方式,采用基于 Feature 的方式进行分工了,基于这点,项目的目录结构也逐渐演变成下面要讲的形式了。

形式二

下面的目录结构就是文章开头所说的我现在通常采用的目录结构。这种形式的主要特点就是更好地支持基于 Feature 进行模块划分和任务指派。在每个 Feature 下,对应的开发人员需要穿透 MVC 整个层次来完成这个功能模块的开发,那么他对于各层的接口也能更高效地开发,不需要的接口不用写,复杂的接口能写的更高效。甚至,开发人员可以根据 Feature 的特点采用更适合的架构,比如 MVVM 架构等等,这样更具灵活性。但是需要关注的是,还是需要提供一定规范限制,保持每个 Feature 下代码结构的清晰,这样也利于同事的查阅、修改和调用。

|—MyProject
    |—ignore-folder
        |—readme.log
        |—3rdparty
        |—data
    |—Utility
    |—Common
    |—Feature
        |—Base
            |—Controller
            |—View
            |—Model
        |—Main
            |—Controller
            |—View
            |—Model
        |—User
            |—Controller
            |—View
            |—Model
    |—Resource
        |—Image
        |—Sound
|—Pods

基本原则

上面的 iOS 项目目录结构不一定适合所有人的想法,关键看你希望用一个结构解决什么问题。就我自己而言,我是希望通过一个良好的项目结构去达到两个目的:

这个结构会在不断遇到问题解决问题的过程中权衡、进化,在这个过程最重要的是能够保持:

关于Xcode的文件夹

Group 和 Folder Reference 的区别

说完目录结构,插一点小话题,说说 Xcode 的文件夹。Xcode 项目的文件夹有 Group 和 Folder Reference 之分。它们的区别在 Xcode Groups vs. Folder References 这篇文章里有详细的讲述。

Group 的缺点如下:

Group 的优点如下:

Folder Reference 的有这些优点:

Folder Reference 的有这些缺点:

在实际使用中,使用 Group 要多得多。

Xcode 项目结构和磁盘文件结构的对应

上面说了 Group 和 Folder Reference 各自的优缺点,在项目中,我习惯上也是不使用 Folder Reference,只用 Group。在 Xcode 项目中创建 Group 的方式有两种:

对照 Xcode 项目的 MyProject.xcodeproj/project.pbxproj 文件可以看到对应着 Folder 的 Group 和直接创建的 Group 的区别就在于前者是用 path 属性去记录,后者是用 name 属性去记录。如下,WebService 是一个不对应 Folder 的 Group,Resource 是一个对应 Folder 的 Group。通过各个结点的父子关系以及 path 属性,Xcode 就能管理好每个文件的 Reference。

45E59EDF18BBA92C00251797 /* WebService */ = {
     isa = PBXGroup;
     children = (
          452183D3195AA18F00679F14 /* CXTaskControlService.h */,
          452183D4195AA18F00679F14 /* CXTaskControlService.m */,
     );
     name = WebService;
     sourceTree = "<group>";
};
45E59EE318BC2B4100251797 /* Resource */ = {
     isa = PBXGroup;
     children = (
          45E59EE418BC2B4100251797 /* Image */,
          45C97CA91900260A0020C517 /* Sound */,
     );
     path = Resource;
     sourceTree = "<group>";
};

对于第一种方式:

对于第二种方式:

根据上面的对比,

Blog

Opinion

Project