要在 SPM 或 Xcode 项目中正确发布和依赖二进制 XCFramework,并确保模块隔离与向后兼容,需要从编译配置、分发策略和版本控制三个维度进行设计。
以下是实现这一目标的专业指南:
一、 发布端的正确姿势:开启“库进化”模式
要保证向后兼容性(即:用新版本编译器编译的 App 能链接旧版本编译器编译的 SDK),必须开启 Library Evolution。
-
开启编译设置: 在构建 XCFramework 的 Target 中,将以下设置设为
YES:BUILD_LIBRARIES_FOR_DISTRIBUTION = YES- 这会生成
.swiftinterface文件(一种文本格式的 Swift 接口声明),它是跨编译器版本兼容的关键,替代了二进制的.swiftmodule。
-
构建命令示例: 使用
xcodebuild archive时,务必带上该参数:Bash
xcodebuild archive -scheme MySDK \ -destination "generic/platform=iOS" \ -archivePath "./archives/ios" \ SKIP_INSTALL=NO \ BUILD_LIBRARIES_FOR_DISTRIBUTION=YES
二、 模块隔离:隐藏实现细节
为了防止 SDK 的内部依赖污染到宿主 App(即模块隔离),应遵循以下原则:
-
避免暴露内部依赖: 如果你的 SDK 依赖了第三方库(如 Alamofire),不要在公开接口(Public API)中暴露这些类型的对象。
- 错误做法:
public func fetchData() -> DataRequest - 正确做法:将其封装在自己的类型中,或通过协议隔离。
- 错误做法:
-
使用访问控制: 严格使用
public而非open(除非你希望宿主 App 继承你的类)。内部使用的工具类务必标记为internal。 -
静态链接 vs 动态链接:
- 如果你的 XCFramework 内部嵌套了其他二进制库,建议采用动态库形式,以避免宿主项目出现“重复符号(Duplicate Symbols)”错误。
三、 在 SPM 中分发:版本化与安全校验
通过 SPM 分发二进制包时,需要创建一个“镜像” Package 仓库。
-
准备远程下载地址: 将生成的
.xcframework压缩成.zip并上传到服务器(如 GitHub Release)。 -
获取 Checksum: 使用终端计算 ZIP 的哈希值,确保传输安全:
Bash
swift package compute-checksum MySDK.xcframework.zip -
编写 Package.swift:
Swift
let package = Package( name: "MySDKDistribution", products: [ .library(name: "MySDK", targets: ["MySDKTarget"]) ], targets: [ .binaryTarget( name: "MySDKTarget", url: "https://example.com/path/to/MySDK.xcframework.zip", checksum: "从上一步获取的哈希值" ) ] )
四、 宿主项目依赖:保证稳定性
作为使用者,如何稳健地引入这些二进制库?
-
版本约束策略:
- 在依赖时优先使用
upToNextMinor(from:)。由于二进制库内部逻辑不透明,跨大版本(Major)甚至次版本(Minor)的变动风险较高。
- 在依赖时优先使用
-
处理符号冲突:
- 如果发现 XCFramework 包含的符号与宿主项目冲突,检查 XCFramework 是否在构建时开启了
Enable Bitcode(虽然苹果已弃用,但在某些旧流程中仍会影响符号导出)。
- 如果发现 XCFramework 包含的符号与宿主项目冲突,检查 XCFramework 是否在构建时开启了
-
向后兼容性验证:
- 定期使用低版本的 iOS 系统进行真机测试。XCFramework 内部的
Info.plist记录了MinimumOSVersion,确保其不高于宿主项目的最低支持版本。
- 定期使用低版本的 iOS 系统进行真机测试。XCFramework 内部的
五、 核心避坑检查清单
- 架构完整性:你的 XCFramework 是否同时包含了
ios-arm64(真机)和ios-arm64_x86_64-simulator(模拟器)?缺少后者将导致 M1/M2 Mac 开发者无法编译。 - Swift 运行时:确保没有链接不必要的系统私有库。
- Module Name:确保 XCFramework 的模块名不会与常见的库(如
Core,Utils)重名,建议加上前缀。
通过这种方式发布的 SDK,既能享受二进制分发的极速构建体验,又能通过 .swiftinterface 获得类似源码分发的版本兼容性。