模块拆分(Modularity)不仅仅是将代码移动到不同的文件夹或 Package,它的核心目标是降低耦合、缩短编译时间、提高代码复用率以及实现并行开发。
在 Swift/iOS 开发中,模块拆分应遵循以下核心原则与依赖设计模式:
1. 模块拆分的核心原则
A. 高内聚,低耦合 (High Cohesion, Low Coupling)
- 高内聚:一个模块应该只做一类相关的事情。例如,
NetworkLayer只负责协议请求,而不应参与解析具体的业务模型。 - 低耦合:模块之间通过**接口(Protocols)**通信,而非具体实现。改变模块 A 的内部代码不应导致模块 B 需要重新编译。
B. 最小化依赖 (Minimize Dependencies)
- 每个模块应尽可能精简。如果一个 UI 组件库依赖了庞大的
RxSwift或Alamofire,那么任何引用该组件库的模块都会被迫引入这些繁重的依赖。
C. 接口与实现分离 (Interface vs. Implementation)
- 将模块拆分为两个 Target:
FeatureInterface(存放 Protocol 和 Data Models)和FeatureImplementation(存放逻辑和 UI)。 - 收益:其他模块只需要依赖轻量的
Interface,这能极大减少编译时的依赖图计算开销。
2. 依赖方向设计:单向依赖原则
依赖方向必须是单向且无环的。如果出现循环依赖(A 依赖 B,B 依赖 A),则说明拆分逻辑失败,需要引入第三个底层模块来承载共有逻辑。
经典分层模型 (Layered Architecture)
在设计依赖方向时,应遵循**“上层依赖下层”**的准则,下层永远不知道上层的存在。
-
App Layer (宿主应用) :
- 职责:依赖管理、生命周期注入、路由配置。
- 依赖方向:依赖所有的 Feature 模块。
-
Feature Layer (业务模块) :
- 职责:具体的业务逻辑(如 Login、Home、Profile)。
- 依赖方向:依赖 Domain 层和 Core 层。业务模块之间禁止直接互相依赖(通过 Coordinator 或 DI 容器通信)。
-
Domain Layer (业务抽象层) :
- 职责:定义全 App 通用的数据模型、存储协议、网络服务定义。
- 依赖方向:只依赖底层基础库。
-
Core / Utility Layer (基础库层) :
- 职责:日志、网络引擎、加密、UI 组件库、扩展。
- 依赖方向:无外部依赖(或仅依赖系统库/第三方库)。
3. 解决业务模块间的“横向通信”
当“购物车模块”需要跳转到“商品详情模块”时,由于它们不能直接依赖,通常采用以下设计:
方案:基于 DI 与路由的依赖注入
- 定义协议:在
Domain或Common模块定义ProductDetailBuildable协议。 - 注册实现:在 App 启动阶段,将
ProductDetailModule注册到依赖注入(DI)容器中。 - 调用:购物车模块通过 DI 容器获取
ProductDetailBuildable的实例,从而实现无直接依赖的跳转。
4. 模块化带来的性能优化技巧
- 增量编译优化:将不常变动的模块(如基础库)封装为 Binary Target (XCFramework) ,可以显著提升日常编译速度。
- 按需链接:在 SPM 中,尽量让模块默认为静态链接(Static Linking),这有助于编译器进行无效代码剔除(Dead Code Stripping),减少 App 体积。
总结:模块拆分 Checklist
- 两个业务模块(Feature)之间是否存在直接引用?(如果有,应通过协议解耦)
- 底层基础库是否包含了业务逻辑?(如果有,应将其上移)
- 是否可以通过
Interface模式缩短依赖链? - 每一个模块是否都有对应的
testTarget?