一、模块边界的本质
模块边界 = “系统中谁能访问谁的内部实现、谁依赖谁、谁可以替换谁”的边界”
特点:
-
隐藏实现
- 模块内部细节不暴露
- 外部只能依赖公开接口
-
单向依赖
- 外部可以依赖模块,但模块不依赖外部(或最少依赖)
-
可替换 / 可测试
- 可以 mock / stub 模块接口
- 替换实现不会影响依赖它的模块
换句话说:模块边界是逻辑概念,不是物理概念。
二、四种常见 iOS/Swift 分割方式的角色
| 技术 | 作用 | 是否核心模块边界 |
|---|---|---|
| Target (Xcode) | 编译单元,生成可执行 / framework | ✗ 物理分包,可帮助实现边界,但不等于逻辑边界 |
| Framework / Static Library | 二进制封装,隐藏实现 | ⚪ 边界工具,可增强封装,但本质是“物理隔离” |
| SPM Package | 包管理单元,可独立编译、依赖管理 | ⚪ 边界工具 + 构建优化,非本质 |
| Protocol / Interface | 抽象接口,暴露模块可调用契约 | ✅ 真正的模块边界核心,决定依赖方向和替换能力 |
核心原则:模块边界是“谁依赖谁的抽象契约”,而不是 target 或 package 本身。
三、为什么 protocol 才是核心
- 隐藏实现
protocol UserRepository {
func fetchUsers() async -> [User]
}
// Module A 依赖 UserRepository,不关心实现
- Module A 不需要知道 Module B 是用网络还是本地 DB
- 边界只通过 protocol 暴露
- 单向依赖 / 可替换
- Module A 只依赖 protocol,不依赖具体实现
- Module B 可以替换实现 → Mock / Stub / 测试方便
- 逻辑解耦
- 即使在同一个 target 内,也可以形成模块边界
- 核心不是物理分包,而是 依赖方向和职责划分
四、Target / Framework / SPM 的作用
- Target:方便 Xcode 构建和隔离
- Framework:物理封装,提供二进制接口,可避免符号泄露
- SPM Package:跨项目依赖管理、版本控制
都是 物理实现手段,但模块边界的逻辑核心是 protocol + 单向依赖 + 隐藏实现
五、实际工程实践原则
- 定义模块边界靠协议
// Module A
protocol AuthenticationService {
func login(username: String, password: String) async -> Bool
}
// Module B 依赖 Module A 的协议
class LoginViewModel {
let authService: AuthenticationService
}
- 物理分割工具只做增强
-
Target / Framework / SPM 用于:
- 隔离编译
- 可替换二进制
- 控制依赖
-
但逻辑边界依然靠协议和依赖方向
- 高频场景
- Feature 模块:用 protocol 定义依赖边界 → target / SPM package 用于构建隔离
- 核心库 / 公共库:SPM package + protocol → 可跨项目复用
六、总结一句话
真正的模块边界 = 抽象接口 + 单向依赖 + 实现隐藏
Target / Framework / SPM package = 辅助物理手段,而非边界本身。
换句话说:
模块边界 ≠ 文件夹 ≠ Target ≠ Package
模块边界 = protocol + 职责 + 依赖方向