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)。

我三套都用过,下面这张图可以帮你快速理解它们的核心差异。

iOS SDK 集成方式对比 iOS SDK 集成方式核心决策流程 iOS 项目 CocoaPods Carthage SPM 自动生成 workspace Podfile 声明依赖 只编译 framework 手动拖入项目 Xcode 原生支持 Swift 5.3+ 优先 推荐场景 CocoaPods:成熟生态,OC/Swift 混合项目;Carthage:偏 Swift、构建快;SPM:新项目首选,Xcode 深度集成 注意:Xcode 13+ 对 SPM 支持已成熟

CocoaPods 集成方式

CocoaPods 是我用得最多的工具,尤其是对接第三方 SDK 的时候。说白了,它帮你自动生成一个 .xcworkspace,把所有依赖库编译进一个 Pods 工程里。

集成步骤:

  1. 在项目根目录创建 Podfile(可以用 pod init 生成)。
  2. 编辑 Podfile,写上你要的 SDK,比如 pod 'AFNetworking'。
  3. 执行 pod install,CocoaPods 会下载依赖并生成 .xcworkspace。
  4. 从此只打开 .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 就行。

集成步骤:

  1. 打开 Xcode,项目 Target → General → Frameworks, Libraries, and Embedded Content 右下角 +。
  2. 选择 Add Other... → Add Package Dependency。
  3. 输入 SDK 的 Git 地址,选择版本规则(Up to Next Major / Exact / Range)。
  4. 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 配置
target 'MyApp' do
  pod 'CoreSDK'
end
target 'MyAppTests' do
  pod 'TestingHelper'
end
给主 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 心里才有底。