iOS开发基础82-iOS项目目录结构:从 MVC 到组件化的工程实践

iOS 项目目录结构完全指南:从 MVC 到组件化的工程实践

一、为什么目录结构是面试高频考点

一句话原理

目录结构是项目的"骨架",面试官问目录结构,本质是在考察你的工程经验、架构思维和团队协作能力——一个有经验的开发者,能通过清晰的目录结构让新人快速定位代码,让团队协作效率最大化。

目录结构的三个核心价值

价值 说明
快速定位 看到一个功能,知道去哪个目录找代码,不用全局搜索
职责清晰 每个目录有明确的职责边界,避免代码乱放、重复造轮子
团队协作 统一的目录规范让多人协作时不会互相覆盖、不会找不到文件

面试时如果被问"你们项目的目录结构是怎样的",不要只说"按 MVC 分的",要讲清楚分层逻辑、每个目录的职责、为什么这么分、遇到了什么问题,这样才能体现经验。


二、两种主流目录结构

结构一:主目录按业务分类,内目录按模块(MVC)分类

一句话原理

先按业务模块分大目录(如发现、关注、我的),每个业务目录内部再按 MVC 分层(Controller、View、Model)。

目录结构示意

MyApp/
├── Main/                    # 主要业务
│   ├── Discover/            # 发现业务模块
│   │   ├── Controller/      # 控制器
│   │   ├── View/            # 视图
│   │   ├── Model/           # 模型
│   │   └── Other/           # 该模块的其他文件(如 ViewModel、工具)
│   ├── Follow/              # 关注业务模块
│   │   ├── Controller/
│   │   ├── View/
│   │   ├── Model/
│   │   └── Other/
│   ├── FriendCircle/        # 简友圈业务模块
│   │   ├── Controller/
│   │   ├── View/
│   │   ├── Model/
│   │   └── Other/
│   ├── Me/                  # 我的业务模块
│   │   ├── Controller/
│   │   ├── View/
│   │   ├── Model/
│   │   └── Other/
│   └── Other/               # 其他公共业务
│       ├── TabBar/          # 自定义 TabBar
│       ├── Navigation/      # 导航控制器
│       ├── Public/          # 公用控制器
│       ├── Login/           # 登录注册
│       ├── Setting/         # 设置
│       ├── Release/         # 发布器
│       └── Other/
├── Expand/                  # 拓展层(基础能力)
│   ├── DataBase/            # 数据库
│   ├── Category/            # 分类
│   ├── Network/             # 网络层
│   ├── Tool/                # 工具类
│   ├── Macros/              # 宏定义
│   └── Const/               # 常量
├── Vender/                  # 第三方库(手动导入的)
│   ├── AFNetworking/
│   └── SDWebImage/
└── Resource/                # 资源文件
    ├── Plist/
    └── Image/

优点

  • 业务聚合度高:一个业务模块的所有代码都在同一个目录下,修改"发现"模块时只需要关注 Discover/ 目录。
  • 符合模块化思维:每个业务模块相对独立,便于后续拆分为独立组件。
  • 新人上手快:按业务找代码,符合人的直觉思维。

缺点

  • MVC 层分散:所有 Controller 分散在各个业务目录中,如果想统一修改所有控制器的基类,需要来回切换。
  • 公共类归属模糊:多个业务模块共用的类(如通用的 TableViewCell)不知道该放哪个业务目录。
  • 容易出现重复代码:每个业务模块都有自己的 Controller/View/Model,可能会重复实现相似的功能。

适用场景

  • 业务模块划分清晰、模块间耦合度低的项目。
  • 中大型项目,团队按业务模块分工(每个人负责一个业务线)。
  • 计划未来做组件化拆分的项目。

结构二:主目录按模块(MVC)分类,内目录按业务分类

一句话原理

先按架构分层分大目录(Controller、View、Model),每个层目录内部再按业务模块细分。

目录结构示意

MyApp/
├── Controller/              # 所有控制器
│   ├── Discover/            # 发现模块的控制器
│   ├── Follow/              # 关注模块的控制器
│   ├── FriendCircle/        # 简友圈模块的控制器
│   ├── Me/                  # 我的模块的控制器
│   ├── Login/               # 登录注册
│   ├── Setting/             # 设置
│   └── Public/              # 公用控制器
├── View/                    # 所有视图
│   ├── Discover/
│   ├── Follow/
│   ├── FriendCircle/
│   ├── Me/
│   ├── Public/              # 公用视图(如通用 Cell)
│   └── Other/
├── Model/                   # 所有模型
│   ├── Discover/
│   ├── Follow/
│   ├── FriendCircle/
│   ├── Me/
│   └── Public/
├── ViewModel/               # MVVM 架构下的 ViewModel
│   ├── Discover/
│   └── ...
├── Expand/                  # 拓展层(同结构一)
│   ├── Network/
│   ├── Tool/
│   ├── Category/
│   ├── DataBase/
│   ├── Macros/
│   └── Const/
├── Vender/                  # 第三方库
└── Resource/                # 资源

优点

  • 架构层集中:所有 Controller 在一起、所有 View 在一起,便于统一管理基类、统一修改。
  • 公共类归属清晰:公用的 Cell、公用的 Model 直接放在 View/Public/、Model/Public/ 下。
  • 符合 MVC/MVVM 的分层思维:架构分层清晰,适合严格按照架构模式开发的团队。

缺点

  • 业务分散:一个业务模块的代码分散在 Controller、View、Model 三个目录中,开发一个功能需要在三个目录间来回切换。
  • 文件查找困难:项目大了之后,Controller 目录下有几十个业务子目录,找一个特定的控制器需要层层展开。
  • 不利于模块化拆分:业务代码分散,后续拆组件时需要从各个层中抽取代码,成本高。

适用场景

  • 小型项目,业务模块少,分层简单。
  • 团队按架构角色分工(有人专门写 View、有人专门写 Model)。
  • 严格遵循 MVC/MVVM 架构,强调分层规范的项目。

两种结构对比总结

对比维度 结构一(业务为主) 结构二(分层为主)
一级目录 业务模块 架构层(Controller/View/Model)
二级目录 MVC 分层 业务模块
业务聚合度 高(一个业务的代码在一起) 低(分散在各层)
公共类归属 模糊(不知道放哪个业务) 清晰(放对应层的 Public 下)
模块化拆分 容易(直接拆业务目录) 困难(需要从各层抽取)
新人上手 快(按业务找) 慢(需要理解分层)
适用项目规模 中大型 小型
团队分工方式 按业务线分工 按架构角色分工

核心建议:中大型项目优先选结构一(业务为主),因为业务聚合度高、便于模块化拆分、团队按业务线分工更高效。小型项目可以选结构二,分层简单清晰。


三、标准目录各层详解

无论哪种结构,通常都会有以下几个核心层级,下面逐一解析每个目录的职责和命名规范。

1. Main / Classes(业务层)

职责:存放所有业务相关的代码,是项目的核心目录。

命名规范:

  • 业务模块名用英文,首字母大写,如 Discover、Follow、FriendCircle。
  • 可以加中文注释(如 Discover(发现)),方便不熟悉业务的人理解,但大型项目或开源项目建议纯英文。
  • 每个业务模块内部按 MVC/MVVM 分层:Controller、View、Model、ViewModel、Other。

子目录说明:

子目录 职责 命名示例
Controller 该业务模块的所有控制器 DiscoverViewController.h、DiscoverDetailViewController.h
View 该业务模块的自定义视图 DiscoverCell.h、DiscoverHeaderView.h
Model 该业务模块的数据模型 DiscoverModel.h、DiscoverItem.h
ViewModel MVVM 架构下的视图模型 DiscoverViewModel.h
Other 该模块特有的工具、管理器等 DiscoverDataManager.h

2. Expand / Common / Base(拓展层 / 基础层)

职责:存放跨业务模块共用的基础能力,是项目的"基础设施"。

常见子目录:

子目录 职责 内容示例
Network 网络层封装 HttpRequestManager、ApiService、NetworkMonitor
Tool 工具类 DateTool、StringTool、ImageTool、SandboxTool
Category 系统类分类 UIView+Frame、NSString+Hash、UIImage+Resize
DataBase 数据库封装 DataBaseManager、CoreDataStack、RealmManager
Macros 宏定义 AppMacro.h(屏幕宽高、颜色)、NotificationMacro.h
Const 常量定义 AppConst.h(通知名、缓存 key)、URLConst.h(接口地址)
Base 基类 BaseViewController、BaseView、BaseModel、BaseCell
Router 路由层(组件化项目) AppRouter、RouterProtocol

命名规范:

  • 工具类以 Tool 结尾,如 DateTool。
  • 管理器以 Manager 结尾,如 HttpRequestManager。
  • 分类采用 类名+功能 格式,如 UIView+Frame。
  • 宏定义全部大写,如 SCREEN_WIDTH。
  • 常量以 k 开头,如 kUserDidLoginNotification。

3. Vender / Vendor(第三方库)

职责:存放手动导入的第三方库源码。

注意:

  • 如果用 CocoaPods / Carthage / Swift Package Manager 管理第三方库,这个目录可以不存在(依赖在 Pods/ 目录或系统管理)。
  • 手动导入的第三方库建议保留原目录结构,不要修改源码,便于后续升级。
  • 对第三方库做了修改的,建议单独建 Vender+Custom/ 目录存放修改后的版本,并注明修改内容。

4. Resource / Assets(资源层)

职责:存放图片、音频、视频、Plist、JSON、字体等资源文件。

常见组织方式:

Resource/
├── Assets.xcassets/      # 图片资源(推荐用 Asset Catalog)
│   ├── AppIcon.appiconset/
│   ├── LaunchImage.imageset/
│   ├── TabBar/            # TabBar 图标
│   ├── Discover/          # 发现模块图片
│   ├── Common/            # 公用图片
│   └── Placeholder/       # 占位图
├── Plist/                 # Plist 配置文件
│   ├── CityList.plist
│   └── EnvironmentConfig.plist
├── JSON/                  # 本地 JSON 数据(如测试数据、配置)
├── Audio/                 # 音频文件
├── Video/                 # 视频文件
├── Font/                  # 自定义字体
└── HTML/                  # 本地 H5 页面

最佳实践:

  • 图片资源优先用 Assets.xcassets 管理,支持 @2x/@3x、深色模式、矢量图等。
  • 图片按业务模块分子目录,避免所有图片堆在一个目录下。
  • 公用图片放 Common/,业务图片放对应业务目录。
  • 资源文件名加业务前缀,如 discover_avatar_placeholder.png,避免命名冲突。

5. Other / Supporting Files(其他)

职责:存放 AppDelegate、main.m、Info.plist 等入口文件和配置文件。

Other/
├── AppDelegate.h
├── AppDelegate.m
├── main.m
├── Info.plist
└── .pch文件(可选,现在不推荐用)

现在 Xcode 新建项目默认把 AppDelegate 和 Info.plist 放在根目录,不需要单独建 Other/ 目录,但如果项目文件多,建一个 App/ 或 Supporting/ 目录统一管理入口文件会更清晰。


四、不同架构下的目录结构

目录结构和项目采用的架构模式紧密相关,下面是常见架构的目录特点。

1. MVC 架构

MyApp/
├── Controller/     # 所有控制器
├── View/           # 所有视图
├── Model/          # 所有模型
└── Expand/         # 基础层

特点:严格按 Model-View-Controller 三层分,结构简单。但 Controller 容易臃肿(Massive View Controller),中大型项目不推荐纯 MVC。

2. MVVM 架构

MyApp/
├── Main/
│   ├── Discover/
│   │   ├── Controller/    # 控制器(负责 UI 逻辑、绑定 ViewModel)
│   │   ├── View/          # 视图
│   │   ├── ViewModel/     # 视图模型(负责业务逻辑、数据处理)
│   │   └── Model/         # 数据模型
│   └── ...
└── Expand/

特点:在 MVC 基础上增加了 ViewModel 层,把业务逻辑从 Controller 中抽离出来,Controller 变轻。目录中每个业务模块下都有 ViewModel/ 子目录。

3. VIPER 架构

MyApp/
├── Main/
│   ├── Discover/
│   │   ├── View/          # 视图(被动显示,通知 Presenter)
│   │   ├── Presenter/     # 展示层(处理 UI 逻辑,调用 Interactor)
│   │   ├── Interactor/    # 交互层(业务逻辑、数据获取)
│   │   ├── Entity/        # 实体(数据模型)
│   │   └── Router/        # 路由(页面跳转)
│   └── ...
└── Expand/

特点:分层最细,每个业务模块下有 5 层(View-Presenter-Interactor-Entity-Router),职责最清晰,但代码量最大、开发成本最高。适合大型团队、长期维护的项目。

4. 组件化 / 模块化架构

中大型项目发展到一定阶段,会做组件化拆分,目录结构会发生本质变化:

MyApp/                     # 主工程(壳工程)
├── App/                   # 主 App 入口
│   ├── AppDelegate
│   ├── MainTabBar/        # 主 TabBar 组装
│   └── AppAssembly/       # 组件注册与组装
├── Modules/               # 业务组件(每个都是独立的 Pod / 库)
│   ├── DiscoverModule/    # 发现组件
│   │   ├── Classes/
│   │   │   ├── Controller/
│   │   │   ├── View/
│   │   │   ├── ViewModel/
│   │   │   └── Model/
│   │   ├── Assets/
│   │   └── DiscoverModule.podspec
│   ├── FollowModule/      # 关注组件
│   ├── MeModule/          # 我的组件
│   ├── LoginModule/       # 登录组件
│   └── ...
├── BasicModules/          # 基础组件(通用能力)
│   ├── NetworkModule/     # 网络组件
│   ├── RouterModule/      # 路由组件
│   ├── ImageModule/       # 图片加载组件
│   ├── DatabaseModule/    # 数据库组件
│   ├── UIKitModule/       # 基础 UI 组件
│   └── ...
└── Podfile                # 依赖所有组件

组件化的核心特点:

  • 每个业务模块是独立的 Pod 库,有自己的目录结构、资源、依赖。
  • 主工程是壳工程,只负责组装各个组件,不包含业务代码。
  • 组件间通过路由(Router)或协议(Protocol)通信,不直接依赖。
  • 基础组件(网络、图片、数据库等)被所有业务组件依赖。
  • 每个组件可以独立编译、独立测试、独立发版。

组件化是中大型项目的必然趋势,但实施成本高,需要团队有一定的架构能力。小项目不要盲目组件化,先把目录结构规范好、模块边界划清,再逐步拆分。


五、目录结构的演进路径

一个项目从初创到成熟,目录结构通常会经历以下演进:

阶段一:初创期(0~3 个月,1~3 人)

MyApp/
├── Controller/
├── View/
├── Model/
├── Tool/
├── Network/
└── Resource/

特点:简单的 MVC 分层,所有文件平铺,快速迭代。问题:业务多了之后文件混乱,找不到代码。

阶段二:成长期(3~12 个月,3~8 人)

MyApp/
├── Main/              # 按业务模块分
│   ├── Discover/
│   │   ├── Controller/
│   │   ├── View/
│   │   └── Model/
│   └── ...
├── Expand/            # 基础层
├── Vender/
└── Resource/

特点:采用"业务为主"的结构一,每个业务模块内部 MVC 分层。问题:业务模块间耦合严重,公共类归属模糊,编译越来越慢。

阶段三:成熟期(1 年以上,8 人以上)

MyApp/                     # 壳工程
├── App/
├── Modules/               # 业务组件(独立 Pod)
├── BasicModules/          # 基础组件
└── Podfile

特点:组件化架构,每个业务模块独立成 Pod,基础能力下沉为通用组件。优势:模块解耦、独立编译、团队并行开发效率高。

不要跳级:初创期不要直接上组件化,成本太高、维护不起。先把目录规范、模块边界做好,等团队和项目规模到了再逐步组件化。


六、命名规范与注释建议

1. 目录命名规范

规范 说明 示例
英文为主 目录名用英文,避免中文路径导致的编译问题 Discover 而非 发现
首字母大写 目录名单词首字母大写(UpperCamelCase) FriendCircle 而非 friendCircle
业务模块名 用业务含义的英文单词或词组 Discover、Follow、Me
架构层名 用标准架构术语 Controller、View、Model、ViewModel
可加中文注释 中小项目可以加中文注释方便理解 Discover(发现)
避免缩写 除非是通用缩写(如 VC、VM),否则用完整单词 ViewController 而非 ViewCtr

2. 文件命名规范

文件类型 命名格式 示例
控制器 业务名+ViewController DiscoverViewController
视图 业务名+View/Cell/HeaderView DiscoverCell、DiscoverHeaderView
模型 业务名+Model DiscoverModel
ViewModel 业务名+ViewModel DiscoverViewModel
工具类 功能+Tool DateTool、StringTool
管理器 功能+Manager HttpRequestManager
分类 类名+功能 UIView+Frame、NSString+Hash
基类 Base+类型 BaseViewController、BaseView

3. 关于中文注释的讨论

原文提到了"目录备注中文名感觉 lo"的讨论,这里给出客观建议:

场景 建议
个人项目 / 小团队 可以加中文注释,降低理解成本,快速上手
中大型团队 / 开源项目 纯英文目录名,通过 README 或文档说明每个目录的职责
代码中的注释 中文注释是必要的,尤其是复杂业务逻辑,方便自己和他人维护
变量和方法名 用英文,不要用拼音或中文翻译的生硬命名,命名不好时加注释说明

核心原则:注释的目的是降低理解成本。如果团队成员都能看懂英文,纯英文更专业;如果有新人或非技术人员需要理解项目结构,加中文注释更友好。没有绝对的对错,适合团队的就是最好的。


七、Swift 项目目录特点

Swift 项目和 OC 项目的目录结构基本一致,但有一些 Swift 特有的点:

1. 没有 .h/.m 文件

Swift 只有一个 .swift 文件,没有头文件和实现文件的分离,文件数量少一半,目录更简洁。

2. 多了一些 Swift 特有的目录

MyApp/
├── MyApp/                  # 主 Target
│   ├── App/
│   │   ├── AppDelegate.swift
│   │   ├── SceneDelegate.swift    # iOS 13+ 的场景代理
│   │   └── MyAppApp.swift         # SwiftUI 项目的 App 入口
│   ├── Main/
│   ├── Expand/
│   └── Resource/
├── MyAppTests/             # 单元测试 Target
├── MyAppUITests/           # UI 测试 Target
├── Packages/               # Swift Package Manager 依赖(如果用 SPM)
├── MyApp.xcodeproj/
├── Podfile                 # 如果用 CocoaPods
└── Package.swift           # 如果用 SPM 管理自己的模块

3. Swift 模块组织

Swift 可以用 import 导入模块,组件化项目中每个组件是一个 Swift Module,目录结构和 OC 组件化类似,但用 Package.swift 或 .podspec 管理依赖。

4. SwiftUI 项目

SwiftUI 项目的目录更简洁,没有 ViewController 层(换成了 View),架构通常是 MVVM:

MyApp/
├── App/
│   └── MyAppApp.swift       # @main 入口
├── Views/                   # SwiftUI 视图
│   ├── Discover/
│   └── ...
├── ViewModels/              # 视图模型
├── Models/                  # 数据模型
├── Services/                # 服务层(网络、数据库等)
└── Resources/

八、常见问题与坑

问题 1:目录结构定了之后还能改吗?

答案:能改,但成本随项目规模递增。

  • 小项目:随时可以重构目录,Xcode 中移动文件不会影响编译(只要更新 group 引用)。
  • 中大型项目:重构目录需要大量修改 #import 路径、更新 Podspec、处理冲突,建议在版本迭代间隙逐步调整,不要一次性大改。
  • 最佳实践:项目初期就规划好目录结构,后续小步调整,避免大规模重构。

问题 2:Xcode 中的 Group(黄色文件夹)和 Folder(蓝色文件夹)有什么区别?

类型 图标 说明 适用场景
Group 黄色文件夹 仅 Xcode 中的逻辑分组,不对应真实磁盘目录,文件编译时会被打平 代码文件(.h/.m/.swift),推荐用 Group
Folder 蓝色文件夹 对应真实磁盘目录,文件保留目录结构,通过路径访问 资源文件(图片、H5、配置文件),需要保留目录结构时用

注意:

  • 代码文件推荐用 Group(黄色),因为 Group 不影响 #import 路径,移动文件不需要改引用。
  • 资源文件如果需要按目录路径访问(如 bundle pathForResource:@"index" ofType:@"html" inDirectory:@"web"),必须用 Folder(蓝色)。
  • 很多团队的做法是:Xcode 中用 Group,但磁盘上也建对应的真实目录,保持一致(通过 "Create groups" + 勾选 "Copy items if needed",并在磁盘上手动建对应目录)。

问题 3:公共类到底该放哪里?

答案:按"被谁使用"决定:

  • 被多个业务模块使用:放 Expand/(基础层)下的对应目录,如通用工具放 Expand/Tool/,通用视图放 Expand/Base/ 或 Expand/UI/。
  • 只被一个业务模块使用:放该业务模块目录下的 Other/ 或 View/ 中。
  • 不确定未来会不会被复用:先放业务模块下,等有第二个模块使用时再下沉到基础层。不要过度设计,不要一开始就把所有类都放基础层。

问题 4:组件化项目中,资源文件怎么管理?

答案:每个组件自己管理自己的资源,放在组件目录下的 Assets/ 中,通过组件的 bundle 访问。主工程的资源放主工程的 Assets.xcassets。

// Swift 中访问组件内的图片
let image = UIImage(named: "icon_discover", in: Bundle(for: DiscoverModule.self), compatibleWith: nil)

组件化资源管理是个大坑,建议用 resource_bundles(CocoaPods)或 resources(SPM)规范管理,避免资源名冲突和找不到资源的问题。

问题 5:目录层级太深怎么办?

答案:控制在 3~4 层以内。

  • 推荐:Main/Discover/Controller/(3 层)。
  • 不推荐:Main/Business/Social/Discover/List/Controller/(6 层,找文件太累)。
  • 如果业务模块特别多,可以在 Main/ 下再按业务域分一层(如 Main/Social/Discover/、Main/Trade/Order/),但最多再加一层。

问题 6:你们项目的目录结构

回答框架:

  1. 整体分层:我们项目采用"业务为主"的结构,分为业务层、基础层、第三方、资源层四大块。
  2. 业务层细节:业务层按业务模块分(发现、关注、我的等),每个模块内部按 MVVM 分层(Controller、View、ViewModel、Model)。
  3. 基础层内容:基础层包含网络、工具、分类、数据库、基类、路由等通用能力。
  4. 为什么这么分:业务聚合度高,便于团队按业务线分工,也为后续组件化拆分做准备。
  5. 遇到的问题和改进:比如公共类归属模糊,我们的做法是先放业务模块,被复用时再下沉;项目大了之后我们做了组件化拆分,每个业务模块独立成 Pod。

九、总结

  • 目录结构没有绝对的好坏,适合项目规模、团队习惯、业务特点的就是好结构。
  • 两种主流结构:
    • 结构一(业务为主):主目录按业务分,内目录按 MVC 分——推荐中大型项目。
    • 结构二(分层为主):主目录按 MVC 分,内目录按业务分——推荐小型项目。
  • 标准四层架构:业务层(Main)、基础层(Expand)、第三方(Vender)、资源层(Resource)。
  • 架构影响目录:MVC 三层、MVVM 加 ViewModel、VIPER 五层、组件化独立 Pod。
  • 演进路径:初创期简单 MVC → 成长期业务为主 → 成熟期组件化,不要跳级。
  • 命名规范:英文为主、首字母大写、业务含义清晰,中小项目可加中文注释。
  • 面试回答:先整体分层、再业务细节、再基础层、再设计原因、再问题改进。

目录结构是项目的"第一印象",也是团队工程规范的体现。花时间规划好目录结构,不是浪费时间,而是为后续的开发效率、可维护性、团队协作打下基础。


posted @ 2017-04-19 08:51  Mr.陳  阅读(1199)  评论(0)    收藏  举报