4. iOS SDK 目录结构解析归纳总结
刚开始接触 iOS SDK 开发时,我面对打包出来的 .xcframework 文件夹总是一脸懵。里面那么多子目录,到底哪些是必须的?哪些可以删?后来踩了几个坑才摸清楚。今天就把我整理出来的经验分享给你。
SDK 框架的核心骨架
一个标准的 iOS 静态库 SDK(以 .xcframework 为例),展开后大概长这样:
- Headers/ — 对外公开的头文件
- PrivateHeaders/ — 私有头文件(一般不开放)
- Resources/ — 图片、bundle、配置文件
- Modules/ — 模块映射文件(用于 Swift 兼容)
- 静态库本体 — 如 .a 或 .framework 里的可执行文件
- dSYM/ — 符号文件,用于符号化崩溃日志
我把常用目录整理成了下面这个表格,你对照着看会清楚很多:
| 目录/文件 | 作用 | 打包时建议 |
|---|---|---|
| Headers | 暴露给外部调用的 .h 文件 | 必须保留 |
| PrivateHeaders | 内部接口或测试用头文件 | 建议剔除或隐藏 |
| Resources | 图片、Storyboard、xcassets 等资源 | 按需保留 |
| Modules | module.modulemap 和 Swift 接口 | 支持 Swift 时必须保留 |
| 静态库本体 | 编译后的二进制(.a 或 .framework) | 必须保留 |
| dSYM | 调试符号文件(回溯崩溃) | 强烈建议保留 |
Headers 与 PrivateHeaders —— 门面与后门
Headers 里放的是你愿意公开给用户的 API。我习惯只把核心功能的 .h 放进去,像内部宏、扩展类这些一律不公开。否则用户一旦误用了私有 API,后续升级很容易出问题。
PrivateHeaders 其实是个「后门」。有些 SDK 会用它导出单元测试用的内部方法,或者给高级用户特殊调用。但我个人建议:正式发布的 .xcframework 最好别带 PrivateHeaders。我曾经就因为这个被人拿去调用私有方法,最后出了问题还被追责,教训深刻。
Xcode 的 Copy Files 阶段,只勾选 "Headers",不勾选 "Private Headers"。或者脚本里直接删除 PrivateHeaders 文件夹,干净利落。
Resources 与 Modules —— 资源与兼容性
如果你的 SDK 用到了图片、nib 文件,甚至字体,那 Resources 目录就少不了。有人说直接把这些文件放到主包里不行吗?可以,但容易和宿主 App 的同名资源冲突。所以苹果推荐你打成 .bundle,然后放在 Resources 里。
Modules 目录里藏着两个重要文件:module.modulemap 和 .swiftinterface。前者让 OC 的 #import <Framework/Framework.h> 自动找到头文件;后者让 Swift 调你的 OC 代码时能生成正确的接口签名。说白了,没有这俩文件,你的 SDK 就只能在纯 OC 项目里跑,Swift 项目根本用不了。
静态库与 dSYM —— 核心与调试支撑
真正编译出来的二进制就是 静态库。在 .framework 里它叫 FrameworkName(没有后缀),在 .a 里它就是 libxxx.a。你可以用 lipo -info 查看它包含哪些架构。
% lipo -info MySDK.framework/MySDK
Architectures in the fat file: MySDK.framework/MySDK are: armv7 arm64 x86_64
dSYM 文件必须和每个架构一一对应。如果 xcframework 里包含了模拟器和真机的 slice,那 dSYM 应该也有两套。我记得有一次线上崩溃堆栈全是偏移地址,就是因为忘了把 dSYM 上传到 Bugly——你想想看,没符号文件,崩溃根本没法定位,项目差点延期。所以,永远保留 dSYM,即便你用的是 Bitcode。
自定义目录结构的建议
除了标准结构,有些团队会加一些自己的目录:
- Include/ — 仅放 .h,与二进制分开
- Lib/ — 多架构的 .a 文件
- Support/ — 配置文件、脚本等
- Version/ — 版本号标记文件(便于 CI 自动化检查)
我个人习惯在 .xcframework 顶层加一个 VERSION 文件,里面只写一行版本号。这样 CI 脚本可以快速读取,不需要解压就可以判断版本,速度很快。
下面这张图把 SDK 目录结构的关系画了出来,你看看就明白了:
深入:dSYM 的放置与验证
很多新手把 dSYM 直接扔到 xcframework 的根目录就完事了。其实不对。苹果要求的格式是 dSYMs/ 文件夹,里面放 FrameworkName.framework.dSYM 这个包。如果你用 Xcode 构建 .xcframework 时指定了 -debug-symbols 参数,它会自动生成正确的目录结构。
dsymutil -o /dev/null - flat MySDK.framework/MySDK。如果没有输出错误,说明 dSYM 可用;否则会提示 "could not find symbols"。遇到这个警告,一定要回头检查 dSYM 路径。
另外我记得有一版 Xcode 13 的 bug:当你同时打多架构的 xcframework 时,dSYM 有时会被重复拷贝。我当时的解决方法是先分别构建每个架构的 framework,再手动用 lipo -create 合并,最后再单独拷贝 dSYM。虽然麻烦,但结果可靠。
总结一下关键点
- Headers 要精简——只放要公开的 API,减少用户误用风险。
- PrivateHeaders 最好去掉——除非你有充分的理由(比如单元测试私有模块)。
- Resources 统一打 bundle——避免资源名冲突。
- Modules 文件支持 Swift 调用——没有它,Swift 项目就没法
import你的 SDK。 - dSYM 必须保留——线上崩溃不符号化就等于白崩。
- 可以自制 VERSION 文件——对 CI 自动化和版本追溯很有帮助。
理解了目录结构,你就能精准控制 SDK 打包时哪些该留、哪些该删。下一刀下得准,编译时间都能省不少。嗯,今天就聊到这里,后面我们再继续看如何优化编译选项。