Swift / SwiftUI 字典。体例对齐站内 Go 关键字篇:每条给语义、多场景示例、边界与坑。
1. 可选值 Optional 与解包四式
描述
Optional 表示「可能有值或 nil」。四种解包:?? 兜底、if let 作用域解包、guard let 提前退出、! 强制解包。SwiftUI 里状态驱动的 UI 尤其要谨慎用 !。
语法和示例
var name: String? = nil
let show = name ?? "匿名" // 1) 兜底
if let n = name { print(n) } // 2) 作用域内有效
guard let n = name else { return } // 3) 之后可直接用
print(name!) // 4) 强制(nil 即崩)
// 可选链
user?.profile?.name
user?.friends?.first?.name ?? "无"
// map 链式(有值才执行)
name.map { $0.uppercased() }
// 多可选组合
if let a = x, let b = y { }注意事项
- ! 只用在「逻辑上绝不可能为 nil」处(如 IBOutlet 未连接完成前的初始化)
- 隐式解包 ! 声明(var x: String!)风险更大,SwiftUI 里几乎不该用
- guard let 的 else 必须退出作用域(return/throw/break/fatalError)
- 可选链中任一环为 nil 整条表达式即 nil,不会崩
- Optional 是枚举 Some/None,可与 switch 匹配
2. @State / @Binding / @Observable – 状态三层
描述
SwiftUI 的核心是「状态驱动 UI」。@State 管视图私有值;@Binding 是父子双向读写;@Observable(iOS 17+)是共享模型,body 只追踪实际读取的属性。
语法和示例
struct Counter: View {
@State private var count = 0 // 视图私有
var body: some View {
Button("加一") { count += 1 }
.badge(count)
}
}
struct Child: View {
@Binding var isOn: Bool // 传入,读写父状态
var body: some View { Toggle("开关", isOn: $isOn) }
}
struct Parent: View {
@State private var on = false
var body: some View { Child(isOn: $on) } // $ 桥接为 Binding
}
// 共享模型(17+)
@Observable class Store { var items: [Item] = [] }
struct ListView: View {
@State private var store = Store() // @State 持有 @Observable
var body: some View { ForEach(store.items) { ... } }
}注意事项
- @State 只应标 private,且只用于当前视图及直接子视图的状态
- @Binding 不能直接赋值初始化(它没有真实存储),必须由父传入
- @Observable 模型用 @State 持有,传子视图用普通属性(引用类型天然共享)
- body 重算规则:只追踪 body 实际读到的属性——未读的属性变化不触发
- 别在 init 里给 @State 赋值(会每次重建覆盖);要初始化用 State(initialValue:)
3. guard 与 defer – 提前退出与清理
描述
guard 把失败路径放前面以减少嵌套,其绑定在函数剩余部分可见。defer 在离开当前作用域时执行(含 return/throw),多个 defer 逆序执行。
语法和示例
func load(key: String) throws -> Data {
defer { print("清理") } // 离开时执行
guard !key.isEmpty else { throw Err.empty }
guard let cached = cache[key] else {
return try fetch(key) // guard 失败必须退出
}
return cached
}
// defer 逆序
func multi() {
defer { print("A") }
defer { print("B") }
} // 输出 B 然后 A注意事项
- guard 的 else 必须退出当前作用域——编译器会强制检查
- defer 在函数/方法/闭包作用域结束时执行,不会因 return 跳过
- 多个 defer 是 LIFO(后进先出),适合资源释放顺序
- defer 里不要再 throw(会覆盖原错误);也不要在循环里堆积大量 defer
4. struct 与 class – 值语义 vs 引用语义
描述
struct 赋值即拷贝(值语义,SwiftUI 首选);class 共享引用、需注意循环引用。enum 适合有限状态集合;protocol 定义能力。
语法和示例
struct Point: Hashable { var x, y: Double } // 值拷贝
var p1 = Point(x: 0, y: 0)
var p2 = p1
p2.x = 1 // p1.x 仍为 0
class Repo {
var cache: [String: Data] = [:] // 引用共享
}
// 循环引用:闭包捕获 self
class Service {
var onDone: (() -> Void)?
func start() {
onDone = { [weak self] in self?.finish() } // 弱引用断开环
}
func finish() {}
}
// 协议组合
protocol Named { var name: String { get } }
protocol Aged { var age: Int { get } }
typealias Person = Named & Aged注意事项
- struct 不能继承;需要继承/身份唯一性时用 class
- class 的闭包捕获 self 会强引用——用 [weak self] 或 [unowned self]
- struct 里修改自身需方法标 mutating
- SwiftUI 的 View 全是 struct(值语义保证 body 可安全重算)
- Copy-on-Write:大数组赋值不立即拷贝,写入时才复制
5. 协议与扩展 – 能力注入
描述
protocol 声明要求;extension 为任何类型(含 class/struct/enum/protocol)添加方法、计算属性、协议默认实现。
语法和示例
protocol Greeter {
func greet() -> String
}
extension Greeter {
func greet() -> String { "hi" } // 默认实现
func loud() -> String { greet().uppercased() } // 扩展方法
}
struct Dog: Greeter {}
Dog().loud() // "HI"
// 给 String 加能力
extension String {
var slug: String {
lowercased().replacingOccurrences(of: " ", with: "-")
}
}
"Hello World".slug // "hello-world"
// 条件遵从
extension Array where Element: Equatable {
func unique() -> [Element] {
reduce(into: []) { if !$0.contains($1) { $0.append($1) } }
}
}注意事项
- extension 不能添加存储属性(只能计算属性)
- 协议默认实现若被子类「意外」覆盖,调用可能不走默认——注意静态分发 vs 动态分发
- where 条件遵从让泛型扩展只在满足约束时可用
- extension 里定义的方法对已有类型全局生效——谨慎命名避免冲突
6. NavigationStack – 值驱动导航
描述
iOS 16+ 的导航容器:NavigationLink(value:) 声明目的地,navigationDestination(for:) 决定跳转目标。path 可绑定实现编程式导航。
语法和示例
// 声明式
NavigationStack {
List(items) { item in
NavigationLink(value: item.id) { Text(item.title) }
}
.navigationDestination(for: Int.self) { id in
Detail(id: id)
}
}
// 编程式(path 绑定)
struct Root: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
Button("去详情") { path.append(42) }
.navigationDestination(for: Int.self) { Detail(id: $0) }
}
}
}注意事项
- NavigationLink(value:) 需要同层有匹配的 navigationDestination,否则点了没反应
- 旧写法 NavigationLink(destination:) 仍可用但会提前构造目标视图(性能差)
- path 类型可用 [Int] 等具体数组,或 NavigationPath(支持混合类型)
- 深链解析要在 navigationDestination 链里逐层匹配类型
7. Task 与结构化并发
描述
async/await 是语法层;async let 并行启动子任务;Task {} 是非结构化入口(不随作用域自动取消);.task 修饰符随视图生命周期自动取消。
语法和示例
// async let 并行
async let a = fetchA()
async let b = fetchB()
let (x, y) = try await (a, b) // 并行等待
// TaskGroup 动态并行
await withTaskGroup(of: Data.self) { group in
for url in urls { group.addTask { try await fetch(url) } }
for await d in group { use(d) }
}
// 视图内:.task 随视图出现/消失自动取消
.task { await model.reload() }
// 非结构化
Task.detached(priority: .background) { heavy() }注意事项
- .task 的闭包不接收 throwing——内部用 try? 或 do/catch(编译错误常见点)
- Task {} 不自动取消;视图消失后可能仍在跑,需手动 cancel 或改 .task
- Task.detached 不继承 actor 上下文(不会自动在 MainActor 上)
- await 点在 actor/MainActor 间切换,注意 UI 更新必须在 MainActor
- 结构化并发保证子任务在作用域结束前完成或被取消
8. actor 与 @MainActor – 数据隔离
描述
actor 保证内部状态串行访问(编译期检查);@MainActor 标记在主线程执行(UI 安全)。Swift 6 严格并发下列表更严。
语法和示例
actor Cache {
private var store: [String: Data] = [:]
func get(_ k: String) -> Data? { store[k] }
func put(_ k: String, _ v: Data) { store[k] = v }
}
// 跨 actor 调用必须 await
let c = Cache()
await c.put("a", data)
let v = await c.get("a")
// UI 层
@MainActor
class ViewModel {
var items: [Item] = []
func load() async {
let fetched = await api.fetch() // 挂起时离开主线程
items = fetched // 回到 MainActor 赋值
}
}注意事项
- actor 的方法从外部调用都是 await(跨隔离域)
- actor 内不要做长时间同步阻塞——会阻塞整个 actor 队列
- @MainActor 类/属性/方法都可用;视图与 Observable 模型建议整体标注
- Sendable 是跨并发域传递的类型约束,Swift 6 检查更严
- 避免在 actor 内持有非 Sendable 的可变外部引用
9. sheet / 模态与列表身份
描述
sheet(item:) 数据驱动弹窗;ForEach 依赖 Identifiable 维持视图身份;动画绑 value 精确触发。
语法和示例
// 数据驱动 sheet
@State private var editing: Doc?
.sheet(item: $editing) { doc in Editor(doc: doc) }
.sheet(isPresented: $show) { Picker() }
.fullScreenCover(isPresented: $show) { Full() }
// 列表身份
List(items) { ... } // items 需 Identifiable
ForEach(arr, id: \.self) { ... } // 无 ID 类型时的兜底
ForEach(items, id: \.id) { ... }
// 身份变化会重建视图并触发动画
withAnimation(.spring) { items.toggleFlag(id) }
.animation(.spring, value: items.count)注意事项
- item 版本在 item 变化时自动重新弹窗——注意别在 body 里生成新 item 导致无限弹
- ForEach 用索引作 id 会导致增删时身份错乱(动画与状态错位)
- 跳转列表的 onDelete/onMove 需在 ForEach 内,不能在 List 外
- 动画加 value 参数避免无差别重放
10. 调试与性能 – _printChanges 与重算隔离
描述
SwiftUI 性能问题多源于无效重算。_printChanges() 打印触发来源;优化路径是减少状态粒度、拆分视图。
语法和示例
var body: some View {
Self._printChanges() // 控制台看是谁触发
content
}
// 拆分隔离:把频繁变化的部分独立成子视图
struct Row: View {
let item: Item // 只依赖稳定数据
@State private var expanded = false // 变化范围限制在本行
}注意事项
- Instruments 的 SwiftUI 模板可看视图更新次数
- 大对象不要放 @State 或 @Observable 的高频属性——拆小或在子视图持有
- LazyVStack/LazyVGrid 只构建可见项,长列表必须用它们
- 避免在 body 内创建 Date()/UUID()——每次重算都变,导致无限刷新