SwiftUI redraw 机制全景解读:从 @State 到 Diffing

作者:unravel2025日期:2025/10/4

为什么 UIKit 程序员总问“我的状态去哪了?”

特性UIKitSwiftUI
视图定义与生命周期视图为类(Class),生命周期明确,长期驻留内存视图为值类型(Struct),每次刷新生成新实例
状态保存方式状态保存在视图对象内部Struct 销毁后,状态需由外部系统(如 ObservableObject、@State 等)托管

SwiftUI 提供了一堆 Property Wrapper 来“假装”状态还在视图里,核心就是 @State

@State 到底做了什么?(4 步流水线)

SwiftUI 把一次刷新拆成 4 个微观阶段:

  1. Invalidation(打脏标)
    对用到的属性插 依赖旗标;值改变时插旗为 dirty。
  2. Recompute(重算 body)
    只重算脏旗波及的 body;没读到值的 State 直接跳过。
  3. Diffing(结构差异)
    旧的 View 树 vs 新的 View 树,找出最小集合。
  4. Redraw(GPU 提交)
    Core Animation 仅把真正改动的图层提交给 GPU。

Attribute 系统:给“视图模板”注水

1struct DemoView: View {
2    // 1️⃣ 在视图首次出现时,SwiftUI 为其创建一个持久化的存储槽位
3    @State private var threshold: CGFloat = 50.0   // ← 生成一个 attribute
4    
5    var body: some View {
6        VStack {                                // ← 生成一个 attribute
7            Button("改变") {
8                threshold = 41.24               // 2️⃣ 写入新值 -> 生成 Transaction
9            }
10            Text("当前阈值 \(threshold)")         // 3️⃣ 读取值 -> 建立依赖
11        }
12    }
13}
14
  • Transaction:同一“事件循环”里所有 State 变化打包成一次事务。
  • Cascade Flag:只要 threshold 被打脏,所有读过它的 attribute 都会被连锁打脏。
  • Rule:body 里没读到 = 不 recomputed。官方 Instrument 里会显示body(skipped)

身份稳定:为什么“同一个”视图才能保持 State

1// ❌ 错误示范:切换分支时 struct 类型相同,但身份不同 -> State 丢失
2struct MyMusic: View {
3    @State private var rockNRoll = false
4    var body: some View {
5        VStack {
6            if rockNRoll {
7                MusicBand(name: "The Rolling Stones") // 新身份
8            } else {
9                MusicBand(name: "The Beatles")       // 另一个身份
10            }
11        }
12    }
13}
14
15// ✅ 正确姿势:保证身份稳定(使用相同视图,只改参数)
16struct MyMusic: View {
17    @State private var rockNRoll = false
18    var body: some View {
19        MusicBand(name: rockNRoll ? "The Rolling Stones" : "The Beatles")
20    }
21}
22

口诀:

“同一视图,不同入参” 用参数传值;

“不同视图” 用 if/else 就会换身份,State 清零。

Body 重算粒度实验

只写不读 → 跳过

1struct MovieDetail: View {
2    let movie: Movie
3    @State private var favoriteMovies: [String] = []
4    
5    var body: some View {
6        VStack {
7            Button("加收藏") {
8                favoriteMovies.append(movie.name) // 只写
9            }
10        }
11    }
12}
13

Instrument 显示:MovieDetail.body [skipped]

读写 → 重算,但子视图可跳过

1var body: some View {
2    VStack {
3        HStack {                       // 👈 重算,因为读 favoriteMovies
4            Text(movie.name)
5            Image(systemName: favoriteMovies.contains(movie.name) ? "star.fill" : "star")
6        }
7        Artwork()                     // 👈 没传参,不 recomputed
8        Synopsis()
9        Reviews()
10    }
11}
12

经验:把“纯展示”拆成无参子视图就能躲过重算。

Equatable:手动告诉 SwiftUI“别算我”

1struct FlightDetail: View, Equatable {
2    let flightNumber: Int
3    let isDelayed: Bool
4    
5    // 自定义相等:只看航班号
6    static func == (lhs: Self, rhs: Self) -> Bool {
7        lhs.flightNumber == rhs.flightNumber
8    }
9    
10    var body: some View {
11        VStack {
12            Text("航班 \(flightNumber)")
13            Text(isDelayed ? "延误" : "准点")
14        }
15    }
16}
17
  • 若 struct 全是 POD 类型(Int/Bool…),SwiftUI 会跳过你的 ==,直接按位比较。
  • 想让自定义相等生效:
    1. 包一层 EquatableView(content: FlightDetail(...))
    2. 或者 .equatable() 修饰符。

@Observable vs ObservableObject:从“对象级”到“属性级”

特性Combine(旧)Observation(新)
监听机制监听 objectWillChange 发布者监听具体属性的 KeyPath 变化
更新范围任意 @Published 属性修改触发整个 body 重算仅读取了被修改属性的 body 部分重算
属性包装器要求需通过 @StateObject/@ObservedObject 管理可观察对象可直接使用 @State var model = MyModel() 声明模型
1@Observable
2final class Phone {
3    var number = "13800138000"
4    var battery = 100
5}
6
7struct Detail: View {
8    let phone: Phone          // 无需 ObservedObject
9    
10    var body: some View {
11        Text("电池 \(phone.battery)") // 只当 battery 变才重算
12    }
13}
14

扩展场景:把知识用到“极端”界面

  1. 万级实时股票列表
  • Model 用 @Observableprice 单独标记;
  • 行视图实现 Equatable 仅对比 symbol + price
  • 收到 WebSocket 推送时只改 price,其余字段不动 → 一行只重算自己。
  1. 复杂表单(100+ 输入框)
  • 把每个字段拆成独立子视图;
  • @FocusState + @Observable FormModel,保证敲一个字只重算当前 TextField
  • 提交按钮用 .equatable() 锁定,输入过程不刷新。
  1. 大图轮播 + 陀螺仪
  • @State 保存偏移;
  • TimelineView 按帧读陀螺仪,但把昂贵的图片解码放到后台 Task
  • 仅当图片索引变化才改 Image(source),避免每帧 diff 大图。

个人总结:从“魔法”到“可预测”

SwiftUI 的刷新机制看似黑盒,实则高度 可确定:

“谁依赖,谁重算;谁相等,谁跳过;谁不变,谁不绘。”

把它当成一个依赖追踪引擎而非“UI 库”,就能解释所有现象:

  • 状态放对位置(身份稳定);
  • 依赖剪到最细(读多少算多少);
  • 比较给到提示(Equatable/Observable);
  • 性能用 Instrument 量化(Effect Graph + Core Animation)。

掌握这四步,SwiftUI 不再是“玄学”,而是可推导、可度量、可优化的纯函数式渲染管道。

参考资料 & 工具


SwiftUI redraw 机制全景解读:从 @State 到 Diffing》 是转载文章,点击查看原文


相关推荐


Vue基础10题:答不上来的,简历别写"熟悉Vue"
小时前端2025/10/3

前言 上周面了个应届生,简历上写着"熟悉Vue框架,有多个项目经验"。我问他v-if和v-show的区别,他说"一个是显示隐藏,一个是条件渲染"。我追问"那什么场景用哪个",他卡壳了,这就是典型的‘背题党’。 这就是大部分人的现状:概念背得滚瓜烂熟,一问实战场景就露馅。 今天这10道Vue基础题,每道题我都会告诉你:面试官为什么问这个、标准答案怎么说、什么回答会让你直接出局。每题都配"速记公式",面试前一晚看这篇就够了。 1. 什么是Vue.js?Vue的核心特性和设计理念是什么? 速记公式:


使用 uv 在 Windows 上快速搭建 Python 开发环境
马诗剑2025/10/2

本教程将指导你如何在 Windows 系统上,使用 uv(一个由 Ruff 的作者开发的极速 Python 打包和项目管理工具)来快速创建虚拟环境并安装项目依赖。uv 可以看作是 venv 和 pip 的一个更快、更现代的替代品。 环境要求 Windows 操作系统 已安装 Python (本教程以 Python 3.12 为例) Windows PowerShell 步骤 1: 安装 uv 首先,我们需要安装 uv。这里提供两种方法。 方法一:官方推荐脚本 (推荐) 从开始


Qwen-Image:开源图像生成新突破 —— 聚焦复杂文本渲染与精准图像编辑
算法打盹中2025/10/2

引言 继Qwen-coding与Qwen-reasoning模型取得突破性进展后,阿里巴巴Qwen团队推出了Qwen-Image模型。作为Qwen系列中的开源图像生成基础模型,该模型在复杂文本渲染与精确图像编辑领域实现了显著技术突破。 图像生成领域的核心挑战 尽管图像生成技术在近年取得长足进步,但特定任务场景下的技术瓶颈仍未完全突破: 在文本到图像生成(T2I)任务中,模型输出与多维度复杂提示词的语义对齐仍是亟待解决的关键问题。即便如GPT Image 1及Seedream 3.0等主流


【stm32】CAN分析仪+TJA1050+单片机通信不上,波特率等等都没问题,usb扩展坞的供电问题,绝了
小冷Hello10/2/2025

我就是,绝了,之前都没问题,好久没弄这个CAN了,然后想着插usb扩展坞有隔离安全一点,搞了半天检查代码有检查硬件,怎么都不行,结果突然拔掉插到电脑是,就又有了,绝!注意电源哈,tja1050要5v供电,单独连接到stlink的5v,因为是CAN是差分信号。,否则会出现重复终端(总阻值变小,例如 30Ω 左右),总线电平和收发都会异常。接收器,这里直接rx-rx,tx-tx,注意要区分跟串口不一样,然后再检查那个上位机和单片机波特率要一致,比如125kb,然后我又插回usb扩展坞,绝,又可以了。


[论文阅读] AI + 软件工程(Debug)| 告别 “猜 bug”:TreeMind 用 LLM+MCTS 破解 Android 不完整报告复现难题
张较瘦_10/1/2025

为解决Android bug报告(尤其不完整报告与高复杂度UI场景)自动复现的挑战——现有RL方法(如ReproBot)语义理解弱、LLM方法(如AdbGPT、ReBL)缺乏全局规划,易陷入UI组合爆炸困境,研究者提出**TreeMind**:首个将**LLM语义推理与定制化蒙特卡洛树搜索(MCTS)** 结合的技术。其核心是通过两个LLM引导智能体(**Expander**在MCTS扩展阶段生成top-k候选动作,**Simulator**在模拟阶段一步前瞻估计复现概率),结合多模态UI输入(文本+截图)


VSCode中Python库识别的设置(关于解决VSCode中Python库引入未下载的库不显示黄色虚线的问题)
岫珩9/30/2025

这篇文章介绍了如何解决VSCode中Python库未下载时不会显示黄色虚线警告的问题。通过分析发现,这可能是由于Pylance插件的配置问题导致的。解决方案包括:首先确保安装了Pylance插件,然后在设置中勾选"Enable Troubleshoot Missing Imports"选项,并检查"Diagnostics Source"是否设置为Pylance。最后重启VSCode即可使未下载的库显示黄色虚线警告。文章配有详细的操作截图指引,帮助读者快速解决问题。


草梅 Auth 1.9.0 发布验证码组件 | 2025 年第 40 周草梅周报
草梅友仁2025/10/6

本文在 草梅友仁的博客 发布和更新,并在多个平台同步发布。如有更新,以博客上的版本为准。您也可以通过文末的 原文链接 查看最新版本。 前言 欢迎来到草梅周报!这是一个由草梅友仁基于 AI 整理的周报,旨在为您提供最新的博客更新、GitHub 动态、个人动态和其他周刊文章推荐等内容。 本周依旧在开发 草梅 Auth 中。 你也可以直接访问官网地址:auth.cmyr.dev/ Demo 站:auth-demo.cmyr.dev/ 文档地址:auth-docs.cmyr.dev/ 本周 草梅


“成章”写作助手开源:中秋赏不成月,那就开源一个 AI 实战项目吧
飞哥数智谈2025/10/7

前一阵的实战系列分享中,我们一块实现了一个 AI 写作助手 —— 成章。 当时就打算开源出来,方便感兴趣的同学学习、二开,但一直被各种事情打断,拖到了现在。 今天,赶上中秋节,但阴天没法赏月,索性整理一下先发布出来,需要的自取哈! 简介 “成章”写作助手是一个专为写作者设计的现代化写作平台,实现了文章编写、AI 辅助写作、图片上传等功能,旨在提供高效、便捷的写作体验。 前端开源地址:gitee.com/FlyFive/che… 后端开源地址:gitee.com/FlyFive/che… 功能


Python私教FastAPI+React构建Web应用03 FARM技术栈介绍
Python私教2025/10/9

写在前面 作者:Python私教-张老师 时间:2025年10月07日 出处:电子书《Build Web Applications with FastAPI, React, and MongoDB》 翻译:英文是手抄的,可能会有错误,大家看到了请谅解一下。中文是使用有道词典翻译的。 改进:其中有些地方为了适配国内的阅读习惯以及录播课程,做了一些优化处理,可能会和原书不一样的地方。 简介:本篇文章主要介绍FARM技术栈,分别讲解FastAPI,React和MongoDB的优势以及组合后


FunASR 前端语音识别代码解析
Cosolar2025/10/10

这是基于Web技术的FunASR( 阿里巴巴达摩院 开源语音识别项目) 前端实现方案,支持麦克风实时录音识别与本地音频文件上传识别两大核心场景。整体功能围绕WebSocket通信、音频数据处理、语音识别结果解析三大关键环节展开,以下从代码结构、核心模块及关键逻辑三方面进行详细剖析。 代码:gitee.com/towao/FunAS… 一、代码整体结构 代码采用原生JavaScript编写,无框架依赖,整体架构清晰,主要划分为初始化配置、UI交互 控制 、音频 数据 处理、WebSocket通信、

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0