Swift / SwiftUI 基础知识参考字典

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()——每次重算都变,导致无限刷新
上一篇
下一篇