iOS 开发
面向有一定编程基础、想系统掌握 iOS 开发的笔记。iOS 开发的主线是 Swift 语言 + SwiftUI(或 UIKit)+ 系统能力(并发/存储/网络),副线是签名、打包与上架流程。示例基于 Swift 6 与 iOS 26 SDK 语境编写,需在 Xcode 中验证。
Swift 语言要点(够用的核心)
Swift 是 iOS 的第一公民语言,先建立四个心智:
- 值类型优先:
struct默认拷贝,class引用共享。写 UI 状态模型优先用 struct,需要共享生命周期再考虑 class; - 可选与解包:
Optional是第一等类型,?声明、if let/guard let解包、??给默认值。避免裸!强解包; - 协议与扩展:协议 = 接口 + 约束,扩展 = 给既有类型加方法。SwiftUI 大量依赖「协议 + 修饰符 + 泛型」组合;
- 并发模型:
async/await是主线,actor保护共享可变状态。Swift 6 严格并发下,跨线程传非Sendable数据是编译错误而非运行时事故。
一个最小数据模型 + 网络调用示例:
struct User: Codable, Identifiable {
let id: Int
let name: String
}
actor UserStore {
private(set) var users: [User] = []
func load() async throws {
let url = URL(string: "https://api.example.com/users")!
let (data, _) = try await URLSession.shared.data(from: url)
users = try JSONDecoder().decode([User].self, from: data)
}
}要点:URLSession.data(from:) 是 async 版本,不用闭包回调;模型 Codable 直接映射 JSON;共享可变状态用 actor 收口,天然串行。
Xcode 工程与依赖
- 工程结构:
.xcodeproj管理 target/签名/构建配置;建议新建工程选 SwiftUI App 模板 + Git; - 依赖管理:Swift Package Manager(SPM)是官方默认,支持从 Xcode 直接添加包;CocoaPods 仅在老项目/个别私有库场景需要;
- 签名:调试用个人/团队自动签名即可;发布签名见打包、签名与发布。
App 生命周期
iOS 应用围绕「前台/后台」切换管理资源:
| 状态 | 含义 | 你要做的 |
|---|---|---|
| Not running | 未运行 | — |
| Inactive | 过渡态(来电、控制中心) | 短暂状态,少做重活 |
| Active | 前台运行 | 正常业务 |
| Background | 退后台 | 保存状态、停止重活、申请后台任务时间 |
| Suspended | 挂起(随时可被杀) | 数据应先于此时保存 |
SwiftUI 里用 scenePhase 感知生命周期:
@Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup {
ContentView()
.onChange(of: scenePhase) { _, phase in
switch phase {
case .background: saveDraft()
default: break
}
}
}
}核心纪律:不要在退后台时才想起保存用户数据——每次都写,而不是在生命周期回调里补。
SwiftUI:状态驱动 UI
SwiftUI 的范式是「UI = f(状态)」:
@State管理视图私有值;@Bindable/$双向绑定;@Observable宏 +@Environment做跨视图共享(iOS 17+ 替代ObservableObject);- 视图 body 是声明,SwiftUI 对比旧状态做增量更新;
- 列表与懒加载用
LazyVStack/List,避免一次性构建大量视图。
@Observable
final class CartModel {
var items: [String] = []
var total = 0
func add(_ name: String) { items.append(name); total += 1 }
}
struct CartView: View {
@State private var cart = CartModel()
var body: some View {
VStack {
Text("共 \(cart.total) 件")
List(cart.items, id: \.self) { Text($0) }
}
.task {
// 视图出现时加载
}
}
}注意:body 里不要写副作用;数据加载放
.task或显式触发点,网络/IO 放到 MainActor 之外的执行上下文。
SwiftUI vs UIKit 选型
| 维度 | SwiftUI | UIKit |
|---|---|---|
| 开发效率 | 声明式,快 | 命令式,样板多 |
| 兼容 | iOS 16+ 体验完整 | 全历史版本 |
| 复杂交互 | 快速成长,个别场景需原生控件兜底 | 控件最全最成熟 |
| 社区与招聘 | 已是新项目默认 | 存量项目与资深生态 |
| 混编 | UIViewRepresentable / UIViewControllerRepresentable 桥接 | 反向桥接也可 |
现状:新 App 默认 SwiftUI;遇到 SwiftUI 覆盖不了的交互,用 UIViewRepresentable 包 UIKit 控件,而不是退回整个 UIKit。
并发与主线程纪律
- UI 必须跑在主线程;耗时工作放后台。现代写法几乎不需要手动 dispatch:
Task { ... }结构化并发;await点不阻塞线程;- 网络/IO 用
async系统 API,天然离开主线程; - 回到 UI 更新:SwiftUI 的视图更新应在 MainActor,网络返回后需回到主上下文再改 UI 状态;
- 自定义后台任务与跨线程传值:遵守
Sendable检查,别用全局可变变量绕道; - 强引用循环:闭包捕获 self 在 [生命周期长的场景] 会泄漏;对 class 上的回调闭包,用
[weak self]并判断存在性。
网络与 JSON
struct API {
static func fetchUsers() async throws -> [User] {
var req = URLRequest(url: URL(string: "https://api.example.com/users")!)
req.timeoutInterval = 15
let (data, response) = try await URLSession.shared.data(for: req)
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return try JSONDecoder().decode([User].self, from: data)
}
}工程化要点(完整见网络层与本地存储):统一错误类型、超时与重试、缓存与弱网策略、请求幂等。Swift 里对应工具包括 URLSession 配置、NWPathMonitor 联网检测、ETag/条件请求。
数据持久化
| 场景 | 方案 |
|---|---|
| 轻量偏好设置 | UserDefaults(小数据;不可存敏感信息) |
| 结构化数据建模 | SwiftData(iOS 17+)或 Core Data(历史项目) |
| 本地数据库需求 | SQLite(经 GRDB 等库) |
| 文件/图片缓存 | 应用沙盒目录 + NSCache / 文件管理 |
| 敏感凭证 | Keychain(见网络层与本地存储) |
从开发到上架
开发 → 归档(Archive)→ TestFlight(内部/外部测试)→ 上架审核 → App Store。签名、证书、描述文件与 CI 流水线的要点在打包、签名与发布,这里只记住一条:签名与描述文件是「设备信任链」,个人调试、真机、TestFlight、上架各需要正确的 Team + 描述文件组合。
常见坑速查
| 坑 | 现象 | 对策 |
|---|---|---|
| 主线程卡顿 | 列表滚动掉帧,大图/解码放主线程 | 图片用异步解码/缩略图,IO 与解码都离开主线程 |
| 强引用循环 | 页面退出后内存不释放 | 回调/定时器/闭包对 class self 用 [weak self] |
| 退后台丢数据 | 用户输入退后台被杀丢失 | 每次变更即保存,而非退后台补存 |
| 强解包崩溃 | 意外 nil 直接 crash | 一律 if let/guard let/??,保留崩溃证据不如显式处理 |
| Codable 字段不一致 | 后端改字段名解析失败 | 用 CodingKeys 映射 + 字段兼容(默认值 + decodeIfPresent) |
| URLSession 缓存失效 | 页面永远旧数据 | 明确缓存策略与 ETag 配合,弱网给「有缓存先展示」 |
| SwiftUI 预览黑屏 | 预览依赖网络/权限 | 预览数据注入 Mock,不要在 preview 里跑真网络 |
| 后台任务超时被杀 | 大上传后台做一半 | 用 BGTaskScheduler 显式申请后台任务并做断点续传 |
| 忽略无障碍 | 动态字体下 UI 溢出 | 用系统字体自适应,@ScaledMetric 适配动态字号 |
| 版本升级裸奔 | 升 iOS/新 SDK 后 API 废弃报错 | 每年随 Xcode 大版本做一次兼容性升级演练 |
检查清单
- [ ] 模型用 struct + Codable,共享可变状态收敛到 actor/观察模型;
- [ ] 网络调用走 async/await,统一超时、错误类型与重试;
- [ ] UI 更新在主线程,耗时工作在后台,无裸
!强解包; - [ ] 用户数据即时保存,敏感信息放 Keychain;
- [ ] 长列表使用 List/LazyVStack,无一次性巨型视图树;
- [ ] 已配置真机调试与自动签名,掌握 Archive → TestFlight 流程。