12-31.【SwiftUI】面对 10 个模块依赖的核心模块,如何设计 API 保持稳定?

2 阅读3分钟

面对 10 个以上模块依赖的核心模块,API 设计已经超出了“写代码”的范畴,进入了**“制定标准”的领域。核心模块的任何变动都会引发巨大的“扇出效应”(Fan-out Effect)**,导致上游编译时间爆炸或运行时行为异常。

要保持 API 稳定,需要构建一套**防御式、可扩展且具备“版本兼容性”**的体系。


1. 最小表面积原则(Minimal Surface Area)

“API 越少,你欠的债就越少。”

  • 隐藏实现细节:将所有内部辅助类、扩展方法设为 internal。只暴露必要的协议和结构体。

  • 不透明类型(Opaque Types) :利用 someany 关键字隐藏具体返回类型。

    • 好处:即使你后续更换了内部的算法或存储引擎,只要接口满足协议,依赖方就不需要任何改动。
  • 强类型枚举而非 String:防止依赖方传递非法参数,将错误拦截在编译期。


2. 利用“版本化代理”处理演进

当一个 API 必须改变时,不要直接修改原函数,而是采用**“标记 -> 新增 -> 迁移”**的三部曲。

  • 使用 @available 降级

    Swift

    @available(*, deprecated, renamed: "newMethod(with:)", message: "此 API 将在 v2.0 移除")
    public func oldMethod() { ... }
    
  • 参数包装器(Options Pattern)

    如果函数参数可能增加,不要直接加参数。改用一个 Configuration 结构体。

    Swift

    // ❌ 不利于扩展
    func setup(id: String, theme: Color)
    
    // ✅ 极具弹性
    struct SetupOptions {
        let id: String
        var theme: Color = .blue
        var cachePolicy: CachePolicy = .default // 新增字段不会破坏旧调用
    }
    func setup(with options: SetupOptions)
    

3. 依赖反转:接口模块化(Interface Segregation)

如果核心模块直接持有大量逻辑,它的体积会迅速膨胀。

  • 提取契约层(Contract Layer)

    创建一个只包含 ProtocolEntity 的轻量级 Target(例如 CoreInterface)。

    • 10 个模块只依赖 CoreInterface,而不依赖 CoreImplementation
    • 结果:你可以在不触发上游重编的情况下,随意重构核心模块的实现细节。

4. 稳定性防护:二元分发策略

针对核心模块,建议采用以下物理隔离手段:

策略做法目的
二进制分发 (XCFramework)将核心模块编译为二进制。物理上禁止依赖方修改代码,显著缩短上游模块编译耗时。
API Snapshot 测试使用工具记录 API 的 Public 签名。在 CI/CD 阶段自动检测是否有非预期的 API 变更。
功能开关 (Feature Flags)API 逻辑内部内置开关。允许新逻辑静默上线,一旦出 Bug 可快速回滚而不必重新发版。

5. 容错设计:防腐层(Anticorruption Layer)

核心模块的 API 应该像“堡垒”一样。

  • 严格的输入校验:不要相信依赖方传进来的数据。在 API 入口处进行合法性检查。
  • 错误类型化:提供详尽的 Error 枚举,让依赖方知道是因为“配置错误”还是“网络超时”,而不是给一个模糊的 NSError

总结:核心模块 API 设计公式

稳定性=协议化(Protocols)+文档化的生命周期(Deprecated)可见性控制(AccessControl)稳定性 = \frac{协议化 (Protocols) + 文档化的生命周期 (Deprecated)}{可见性控制 (Access Control)}