本节摘要:本节把 Xcode 工作台的四个区域补全,讲清 Scheme 与构建配置的作用,再用 Swift Package Manager 完成依赖的声明式管理——添加第三方库、声明本地模块包,最后给出排片 App 的模块化拆分方案。结论先行:依赖靠声明不靠拷贝,模块边界就是测试边界。
1.1 节只开了工作台的两盏灯(导航器与调试区),终幕把整个操作台盘亮。
| 区域 | 位置 | 日常用途 | 高频操作 |
|---|---|---|---|
| 导航器 | 左侧 | 文件树、符号跳转、断点列表 | 文件与符号的定位 |
| 编辑区 | 中央 | 写代码、辅助编辑器对照头文件 | 逐行修改 |
| 检查器 | 右侧 | 界面文件的属性、帮助文档 | 改 storyboard 属性、查 API |
| 调试区 | 底部 | 运行日志、变量监视、内存图入口 | 看输出、逐步执行 |
三个高频入口值得记住:Cmd+R 运行、Cmd+B 仅编译(快速验证类型检查,比跑模拟器快得多)、Cmd+Shift+O 按名字跳转到任意文件或符号。Scheme(方案)决定"运行时用哪个配置、要不要带断点、测试跑哪些"——Debug 配置带调试符号跑得慢但可断点,Release 配置带优化跑得快,发布前务必用 Release 配置自测一遍。
排片页要显示星级的第三方控件,旧时代的做法是把源码文件夹拖进工程——升级靠手工覆盖、冲突靠运气。SPM(Swift Package Manager)把依赖变成声明:
// 工程的包清单片段(添加依赖后自动生成,也可在界面上点选) // dependencies 一段记录了来源与版本约束: // .package(url: "仓库地址", from: "1.2.0") // target 依赖一段记录谁用到它: // .product(name: "StarRating", package: "starrating") // 界面操作路径:工程设置 → 依赖页 → 加号 → 填地址 → 选版本策略
添加之后,Xcode 解析清单、拉取源码、编译成模块,工程里直接 import StarRating 就能用。版本约束三档语义:from: "1.2.0"(1.x 内自动升级,兼容才动)、exact:(锁死)、分支或提交(内部联调)。原则:对外库用 from 约束享受修复,关键库必要时锁 exact 防意外。
SPM 不只管远程库,本地包是模块化的利器:把"核心模型"(第3章的 Session、Hall、协议们)抽成一个本地包,界面层通过依赖引用它。操作:新建包目标 → 把模型源文件移入 → 在 App 目标依赖里声明。收益立竿见影——模型层无法再随手 import UIKit(依赖方向被强制约束),测试可以只针对模型包跑(8.2 节的测试边界),将来做多端(iOS、macOS)时模型层直接复用。
背景:排片 App 已经长出第3章的模型、第2章的错误处理、第5章的加载器,全挤在一个 App 目标里,界面代码能随意摸到模型内部。操作:拆成"核心模型包 + 界面层":
// 本地包结构(示意): // CinemaCore/ // 排片模型:Session、Hall、SessionPhase(第3章) // 错误定义:BoardError(第2章) // 加载协议:BoardDataSource(第6章关联类型) // LighthouseCinema(App 目标)/ // 界面:BoardView(第7章 SwiftUI) // 依赖声明:CinemaCore // 模型包对外的门面(内部实现用 internal 默认隔离,第3.3 节的访问控制) public struct BoardFacade { public let sessions: [Session] public init(sessions: [Session]) { self.sessions = sessions } public func urgentOnes(limit: Int) -> [Session] { sessions.filter { $0.seatsLeft <= 5 }.prefix(limit).map { $0 } } } // 界面层的使用:只看得见门面,摸不到内部细节 let board = BoardFacade(sessions: [ Session(filmName: "雾中灯塔", hall: .imax, minutes: 118, seatsLeft: 4), Session(filmName: "星际列车", hall: .dolby, minutes: 142, seatsLeft: 30), ]) print("告急场次 \(board.urgentOnes(limit: 3).count) 场") // 输出:告急场次 1 场
结果解读:界面层依赖的是 BoardFacade 而不是散装的 Session 数组——将来余票计算规则变了,改一处门面即可。变式:三模块版把"网络层"再独立成包(接口对接第7.3 节的老 SDK 桥接),模型、网络、界面三层依赖单向流动;团队协作时各包独立提交,冲突面大幅缩小。
⚠️ 常见坑一:App 目标直接依赖本地包里的具体文件(绕过 import),模块边界形同虚设——本地包必须通过公开接口消费。坑二:滥用 exact 锁版本,一年后所有依赖集体过期,升级变成大工程。
💡 关键直觉:SPM 清单是依赖的账本,与第4章 ARC 的账本异曲同工——谁依赖谁、范围多大,写成声明交给工具记账,别用脑子记。模块拆分的判断标准也只有一个:这两块代码会不会被不同的测试、不同的平台、不同的人分别改动。
工作台亮了,下一节是彩排场与监控室:给核心逻辑写测试、用断点与内存图排障,最后拿一张终检清单把全剧复盘一遍。