iOS SwiftUI 状态与数据流

举报
yd_223002268 发表于 2026/09/22 08:54:14 2026/09/22
【摘要】 iOS SwiftUI 状态与数据流SwiftUI 通过状态变化重新计算视图描述。页面出现重复请求、输入回退或对象被反复创建,通常不是渲染机制本身的问题,而是状态所有权没有定义清楚。每一份可变状态都应有唯一持有者,子视图通过值或绑定读取与请求修改。 一、先确定状态所有者可以先问三个问题:谁创建这份状态。谁决定它何时销毁。哪些视图只能读取,哪些视图可以修改。局部开关由当前视图持有,页面业务状...

iOS SwiftUI 状态与数据流

SwiftUI 通过状态变化重新计算视图描述。页面出现重复请求、输入回退或对象被反复创建,通常不是渲染机制本身的问题,而是状态所有权没有定义清楚。每一份可变状态都应有唯一持有者,子视图通过值或绑定读取与请求修改。

一、先确定状态所有者

可以先问三个问题:

  • 谁创建这份状态。
  • 谁决定它何时销毁。
  • 哪些视图只能读取,哪些视图可以修改。

局部开关由当前视图持有,页面业务状态由页面模型持有,全局账户状态由应用级容器持有。不要因为多个地方要读取,就把所有状态提升到全局。

二、State 管理局部值

struct FilterChip: View {
    @State private var expanded = false

    var body: some View {
        Button(expanded ? "收起" : "展开") {
            expanded.toggle()
        }
    }
}

State 的存储由 SwiftUI 管理,适合视图内部且生命周期一致的简单值。初始化后不应把它当成持续同步外部参数的普通属性。

三、Binding 传递修改能力

子视图不拥有值,但需要修改父级状态时,可以接收绑定:

struct QuantityEditor: View {
    @Binding var quantity: Int

    var body: some View {
        HStack {
            Button("减少") {
                quantity = max(1, quantity - 1)
            }
            Text(String(quantity))
            Button("增加") {
                quantity += 1
            }
        }
    }
}

如果子视图只需要触发一个动作,而不需要任意修改值,传入语义化闭包通常比暴露整个绑定更安全。

四、StateObject 持有引用模型

视图负责创建观察对象时,使用 StateObject 保持对象身份:

struct OrderListScreen: View {
    @StateObject private var viewModel: OrderListViewModel

    init(repository: OrderRepository) {
        _viewModel = StateObject(
            wrappedValue: OrderListViewModel(repository: repository)
        )
    }

    var body: some View {
        OrderListContent(state: viewModel.state)
            .task {
                await viewModel.loadIfNeeded()
            }
    }
}

如果对象由父级创建并传入,子视图使用 ObservedObject 观察即可,不应再次声明所有权。

五、保持页面状态互斥

enum OrderListState: Equatable {
    case idle
    case loading
    case content([OrderRowModel])
    case empty
    case failed(String)
}

一个枚举比多个布尔属性更能表达页面当前阶段。新增状态时,switch 会提示未处理分支。

页面模型应位于主执行域:

@MainActor
final class OrderListViewModel: ObservableObject {
    @Published private(set) var state: OrderListState = .idle
    private let repository: OrderRepository

    func loadIfNeeded() async {
        guard case .idle = state else { return }
        await load()
    }
}

六、避免在 body 中执行副作用

body 可能被多次计算,不应在其中直接发起请求、写存储或发送统计。副作用放在 task、事件闭包或页面模型方法中,并保证重复触发策略明确。

标识变化时可以让任务随标识取消并重新执行,但只有业务确实需要跟随该值刷新时才使用。

七、区分派生数据与原始状态

能从现有状态计算出的值无需重复保存:

var totalPriceText: String {
    priceFormatter.string(
        fromCent: items.reduce(0) { $0 + $1.totalInCent }
    )
}

计算昂贵时可以在模型转换阶段缓存结果,但不要同时维护多个可能不一致的真相来源。

八、环境适合稳定共享依赖

主题、账户会话或导航协调器可以通过环境传递,但环境值过多会隐藏组件真实依赖。业务核心依赖优先通过初始化参数明确传入。

预览和测试中也要提供所需环境值,否则视图可能只在完整应用环境中可用。

九、处理一次性事件

弹窗、导航和提示不是长期页面状态。如果把“是否跳转”作为可重复观察的布尔值,视图重建后可能再次执行。

可以让路由由协调者处理,或把流程建模为明确阶段,并在动作完成后消费。选择应遵循项目现有导航架构。

十、兼容与测试

SwiftUI 状态相关能力随系统版本演进。项目最低版本不支持的新属性包装器不能直接使用,应沿用现有 ObservableObject 等兼容方案。

测试重点包括:

  • 视图重新计算时模型不会重复创建。
  • 首次进入只加载一次。
  • 参数改变后是否按业务刷新。
  • 子视图只能修改允许状态。
  • 取消任务不会显示普通错误。
  • 页面重建后一次性事件不会重放。

总结

SwiftUI 数据流的核心是唯一所有权。局部值使用 State,外部可变值通过 Binding,引用模型由创建者使用 StateObject 持有。副作用离开 body,页面状态保持互斥,再按最低系统版本选择兼容能力,视图更新就会更可预测。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。