
引子:从属性包装器到 Swift 宏
在 Xcode 27 及其配套的系统版本中,SwiftUI 对 @State 的底层实现进行了一项重大重构:@State 从传统的属性包装器(Property Wrapper)转为了 Swift 宏(Macro)。
这一改变看似只是底层的技术演进,但它直接解决了一个困扰开发者多时的性能隐患------特别是当 @State 与 iOS 17 引入的 @Observable 框架配合使用时。
本文将对这一变化的背景、影响以及适配方法进行深度解析。
让我们赶快来一窥究竟吧!😉

一、 历史遗留痛点:非预期的重复初始化
在 SwiftUI 的设计哲学中,View 是值类型(Struct),它的创建与销毁极其频繁。
而真正维持 UI 状态的"状态槽(State Storage)"则独立于 View 树之外,由 SwiftUI 的内部属性图(Attribute Graph)统一管理。
在旧版本(Xcode 26 及更早版本)中,由于 @State 是基于属性包装器实现的,其默认值的求值并非延迟进行。
以下面这段经典代码为例:
swift
@Observable
final class StickerStore {
init() {
print("StickerStore init")
}
}
struct StickerStoreView: View {
@State private var store = StickerStore()
var body: some View {
Text("Hello")
}
}
当父视图刷新导致 StickerStoreView 重新初始化时,以下情况会交替发生:
StickerStoreView作为一个 Struct 被重新构建。- Inline 赋值语句
StickerStore()强制执行,在堆(Heap)上创建了一个新的StickerStore实例。 - SwiftUI 的状态管理机制在比对后发现,该状态槽中已存在一个早期初始化的旧实例。
- SwiftUI 选择保留旧实例,并立即将刚刚创建的新实例丢弃。
形象的"栗子":
这就像你已经购买了一台冰箱并放在厨房中(状态已持久化)。
但每次你进厨房(视图重构)时,系统都会强制你自动下一单买一台新冰箱,等快递送到门口后,你再看一眼说"不需要了"并将其拒收。
这不是没事找事吗?
虽然垃圾回收机制会处理掉这些转瞬即逝的临时对象,但对于一些初始化逻辑较重(如包含磁盘 I/O 或复杂数据解析)的 @Observable 对象,这种隐式的重复初始化会带来不必要的性能开销与 CPU 抖动。

二、 解决方案:宏驱动下的延迟加载(Lazy Evaluation)
随着 @State 的宏化,其底层的初始化行为转为了延迟求值(Lazy Evaluation)。
swift
@State private var store = StickerStore()
在新版编译器与运行时下,上述表达式中的 StickerStore() 只有在 SwiftUI 为该视图分配底层状态存储(State Storage)时,才会真正执行一次。
在后续的视图结构重建(Reinitialization)中,由于底层存储已经建立,该初始化表达式将被跳过,不再会平白创建无用对象并立即丢弃。
兼容性与技术定位:
- 回溯支持: 这一优化行为不仅适用于新版系统,还被回溯支持到了支持
@Observable首次引入的系统版本(如 iOS 17、macOS 14 等)。 - 定位区分:
- 对于传统的
ObservableObject方案,依然建议使用@StateObject(其通过@autoclosure天然支持延迟初始化)。 - 对于 iOS 17+ 现代路线下的
@Observable类,使用@State进行局部持久化是官方推荐的标准实践。 - 对于普通值类型(如
Int、String、Struct),该变化对于性能的边际改善较小,但语义上更为严谨。
- 对于传统的

三、 适配指南:警惕源码级破坏性变更
伴随宏化而来的不仅是性能提升,还有严谨性的升级。
在 Xcode 27 中,如果代码中同时存在"声明期默认值"和"自定义构造器赋值",可能会引发编译期错误。
❌错误示范(引发 use before initialization 报错)
swift
struct MyView: View {
// 1. 在这里声明并提供默认值
@State private var store = Store()
init(id: String) {
// 2. 又在自定义 init 中通过包装器 backing field 进行赋值
_store = State(initialValue: Store(id: id))
}
var body: some View {
Text("Hello")
}
}
在旧版属性包装器实现中,此写法虽然冗余,但编译器通常不报错。
但在宏化后,宏展开生成的辅助属性与逻辑会由于多重初始化逻辑冲突,导致 Xcode 报出 "use before initialization" 错误。
正确的解决方案
如果确定要在自定义 init 中动态初始化 @State 的值,应当彻底移除属性声明处的默认值,仅保留类型声明:
swift
struct MyView: View {
// 仅声明类型,不赋默认值
@State private var store: Store
init(id: String) {
// 在 init 中完成唯一一次初始化
_store = State(initialValue: Store(id: id))
}
var body: some View {
Text("Hello")
}
}

四、 总结
此次 @State 的宏化,是 SwiftUI 数据流生态的一次底层补齐。
以前为了规避重构带来的性能损耗,社区中诞生了诸如将初始化推迟到 .task 或者是自定义 @LazyState 等规避性方案。
如今,苹果通过底层的宏编译技术将 @Observable 放置于 @State 的行为逻辑,彻底修正为符合开发者直觉的"仅初始化一次"语义。
在进行新版本 SDK 适配时,建议关注现有的 init 逻辑,及时清理冗余初始化代码,保障工程在现代编译器下的编译顺畅。
感谢宝子的观赏,我们下次见!😎