iOS SDK 基础:项目结构与依赖管理
说到 iOS 项目结构,我见过不少刚转 iOS 的同事第一眼就懵了。.xcodeproj、.xcworkspace、Podfile…… 到底谁管谁?
我自己的经验是,先把这几个文件的关系理清楚,后面集成 SDK 才不会掉坑里。
iOS 项目结构概览
一个标准的 iOS 项目,最核心的文件就三个:
- .xcodeproj — Xcode 工程文件,记录所有源代码、资源、编译设置。
- .xcworkspace — 工作空间文件,能包含多个工程和 Pods 工程(用了 CocoaPods 就必须打开这个)。
- Info.plist — 应用配置清单,Bundle ID、版本号、权限声明都在这里。
你想想看,三者的关系就像一个 主项目 + 一堆外挂。.xcworkspace 是总调度,里面装着你的主工程和所有依赖库工程。
经验之谈:我遇到过有人直接用 .xcodeproj 打开项目,结果 Pods 的库全报红——因为 .xcodeproj 不知道有 Pods 的存在。记住,只要用了 CocoaPods,请双击 .xcworkspace。
三种集成方式,选哪个?
iOS 的依赖管理工具,主流就三个:CocoaPods、Carthage、Swift Package Manager(SPM)。
我三套都用过,下面这张图可以帮你快速理解它们的核心差异。
CocoaPods 集成方式
CocoaPods 是我用得最多的工具,尤其是对接第三方 SDK 的时候。说白了,它帮你自动生成一个 .xcworkspace,把所有依赖库编译进一个 Pods 工程里。
集成步骤:
- 在项目根目录创建
Podfile(可以用pod init生成)。 - 编辑 Podfile,写上你要的 SDK,比如
pod 'AFNetworking'。 - 执行
pod install,CocoaPods 会下载依赖并生成.xcworkspace。 - 从此只打开
.xcworkspace写代码。
Podfile 示例
platform :ios, '11.0'
use_frameworks!
target 'MyApp' do
pod 'Alamofire', '~> 5.6'
pod 'SDWebImage', '~> 5.0'
pod 'MyPrivateSDK', :path => '../LocalSDK'
end
我曾经在升级 Alamofire 时遇到过版本冲突——老项目里同时引用了 Alamofire 4.x 和另一个依赖它 5.x 的库,导致 pod install 报依赖树错误。解决方法是锁定版本,或者用 pod update 只升级指定库。
Carthage 集成方式
Carthage 的理念是“最小介入”——它只帮你把依赖库编译成 .framework,然后你自己拖进项目。不生成 workspace,不改动 Xcode 设置。
集成步骤:
- 创建
Cartfile,写上依赖,例如github "Alamofire/Alamofire" ~> 5.6。 - 执行
carthage bootstrap --platform iOS或carthage update。 - 把生成的
.framework拖进 Xcode 的 Frameworks, Libraries, and Embedded Content。 - 添加 Run Script 阶段:
/usr/local/bin/carthage copy-frameworks,配置输入文件路径。
避坑提醒:Carthage 构建出来的 framework 如果包含模拟器架构,上架 App Store 前必须用 lipo 去除 x86_64 slice。我在 CI 上挂了两次都是因为这个。
Swift Package Manager (SPM) 集成方式
SPM 是 Apple 官方亲儿子,从 Xcode 11 开始内建支持。它的集成方式最简单——直接在 Xcode 的 File → Add Packages... 输入仓库 URL 就行。
集成步骤:
- 打开 Xcode,项目 Target → General → Frameworks, Libraries, and Embedded Content 右下角 +。
- 选择 Add Other... → Add Package Dependency。
- 输入 SDK 的 Git 地址,选择版本规则(Up to Next Major / Exact / Range)。
- Xcode 会自动解析依赖并集成到项目中。
对于库作者来说,需要在 Package.swift 中声明 products 和 targets:
// swift-tools-version:5.5
import PackageDescription
let package = Package(
name: "MySDK",
platforms: [.iOS(.v13)],
products: [
.library(name: "MySDK", targets: ["MySDK"])
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.6.0")
],
targets: [
.target(name: "MySDK", dependencies: ["Alamofire"]),
.testTarget(name: "MySDKTests", dependencies: ["MySDK"])
]
)
我的习惯:新项目我现在默认选 SPM。一来不需要额外安装工具,二来 Xcode 的依赖解析和缓存做得越来越好。但注意——如果你的 SDK 用了 #if canImport(...) 条件编译,需要确保 SPM 能识别平台。
Podfile 配置要点
虽然 SPM 越来越流行,但很多企业级 SDK 和遗留项目还是 CocoaPods 为主。我这边把 Podfile 最常用的配置整理成一张表:
| 配置项 | 示例 | 说明 |
|---|---|---|
platform |
platform :ios, '12.0' |
指定最低 iOS 版本,SDK 会对齐这个基线 |
use_frameworks! |
use_frameworks! 或 use_frameworks! :linkage => :static |
控制 Pod 构建为动态 framework 还是静态库。SDK 适配时常用静态库避免冲突 |
inhibit_all_warnings! |
inhibit_all_warnings! |
屏蔽 Pod 项目的编译警告,让 Xcode 控制台更清爽 |
| 多 Target 配置 |
|
给主 App、Extension、Test 分别配不同依赖 |
| 本地路径 | pod 'MyLocalSDK', :path => '../MyLocalSDK' |
调试阶段直接指向本地源码目录,方便断点调试 |
| Git 仓库 + 分支 | pod 'PrivateSDK', :git => 'https://git...', :branch => 'develop' |
私有 Pod 或需要测试特定分支时使用 |
特别提醒:use_frameworks! 加上 :linkage => :static 是近年来很多 SDK 推荐的方式。静态链接能避免动态库的加载顺序问题,也减少 App 启动耗时。我自己在适配一个大型日志 SDK 时就因为用了动态库,导致多个 extension 同时加载时发生符号冲突——切到静态库就解决了。
小结
三种集成方式各有适用场景。CocoaPods 生态最全,适合对接历史遗留或闭源 SDK;Carthage 轻量但需要手动配置;SPM 是未来趋势。我的建议是:新 SDK 优先支持 SPM,同时保留 CocoaPods 兼容。这样你不需要在 Podfile 里绞尽脑汁处理版本冲突——还是那句话,先把项目结构搞明白,后面集成 SDK 心里才有底。