@Observable 遇上属性包装器:一键绕过‘计算属性’禁令的 Swift 5.9 实战技巧

在 Swift 5.9 引入的 @Observable 宏(Observable framework)让"全部属性默认被观察"成为可能,但也带来了一个副作用:

被宏展开后,所有存储属性都变成了计算属性,于是再给它们加自定义 Property Wrapper(如 @Injected@UserDefault 等)就会直接报错:

vbnet 复制代码
Property wrapper cannot be applied to a computed property

下面给出原因剖析 + 最小修复示例 + 两种长期策略,让你既能享受 @Observable 的简洁,又能继续用属性包装器做 DI、缓存、格式化等横切逻辑。

先复现问题

swift 复制代码
@Observable
final class ViewModel {
    var data: [String] = []
    
    @Injected                 // ❌ 编译失败
    private var dataProvider: ProviderProtocol
}

展开后的大致伪代码:

swift 复制代码
final class ViewModel: Observable {
    var data: [String] {
        get { _storage.data }          // 计算属性
        set { _storage.data = newValue }
    }
    // 同样,dataProvider 也被转成计算属性 → 无法附加 @Injected
}

最小修复:@ObservationIgnored

苹果提供了忽略观察的宏:@ObservationIgnored

作用:让 @Observable 不要把该属性转成计算属性,保持原样(存储属性),于是就能继续挂 Property Wrapper。

swift 复制代码
@Observable
final class ViewModel {
    var data: [String] = []
    
    @ObservationIgnored      // ✅ 1. 先忽略观察
    @Injected                // ✅ 2. 再挂自定义包装器
    private var dataProvider: ProviderProtocol
    
    func loadData() {
        data = dataProvider.getData()
    }
}
  • 观察链:data 变化仍能触发 SwiftUI 刷新;
  • 注入链:dataProvider 仍是 @Injected 托管的存储属性;
  • 零成本:@ObservationIgnored 编译期生效,运行期无额外开销。

原理速览:宏 vs 属性包装器

维度 Property Wrapper Macro(@Observable、@ObservationIgnored)
执行时机 运行期 编译期
产物 生成存储 + 计算属性 生成计算属性 + 观测逻辑
能否叠加 ❌ 计算属性上不能再挂包装器 ✅ 宏可组合
典型用途 注入、缓存、格式化 观察、代码生成

结论:属性包装器与宏不是替代关系,而是互补工具;冲突时先用宏"放行",再用包装器"加功能"。

长期策略

策略 A:包装器内移------把 @Injected 放到下层类型

swift 复制代码
@Observable
final class ViewModel {
    var data: [String] = []
    
    // 不再直接注入,而是持有一个"已注入"的对象
    private let repo = DataRepository()   // 内部已用 @Injected
}
  • 优点:ViewModel 代码干净,100 % 被观察
  • 缺点:需要多一层类型

策略 B:自定义宏------用 SwiftSyntax 写 @InjectedMacro

swift 复制代码
@Observable
final class ViewModel {
    var data: [String] = []
    
    // 未来可能出现官方或第三方注入宏
    #Injected(.singleton)
    private var dataProvider: ProviderProtocol
}
  • 优点:编译期展开,无运行时反射,性能更好
  • 缺点:目前需自己实现

实战小结(Copy-Paste 模板)

swift 复制代码
import Observation
import Foundation

// 1. 定义注入协议(示例)
protocol ProviderProtocol {
    func getData() -> [String]
}

// 2. 自定义 Property Wrapper
@propertyWrapper
struct Injected<T> {
    var wrappedValue: T
    init() {
        // 简单演示:从全局容器解析
        self.wrappedValue = DIContainer.shared.resolve()
    }
}

// 3. 可观察模型
@Observable
final class ViewModel {
    var data: [String] = []
    
    @ObservationIgnored   // ← 关键:忽略观察
    @Injected             // ← 继续用包装器
    private var dataProvider: ProviderProtocol
    
    func loadData() {
        data = dataProvider.getData()
    }
}

一句话记住

只要看到 "Property wrapper cannot be applied to a computed property"

立刻想:"先 @ObservationIgnored 忽略观察,再挂包装器" ------ 问题秒解。

相关推荐
人月神话-Lee21 小时前
WWDC26 深度解析:如何在 iOS 27 中打造“秒开”的相机体验
ios·swift·相机·wwdc·用户体验
Tr2e1 天前
🐱 从 0 到 1:用 Swift 手搓一个 macOS 桌面宠物(附源码)
macos·ios·swift
人月神话-Lee2 天前
【WWDC】Core AI:iOS 端侧大模型新纪元
人工智能·ios·ai·swift·wwdc·core ai
2501_916007472 天前
iOS 开发工具选择指南 从编辑器、编译器到自动化构建
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
Fatbobman(东坡肘子)2 天前
WWDC 2026 初印象:符合预期,但更务实 -- 肘子的 Swift 周报 #139
人工智能·macos·ios·swiftui·swift·wwdc
大熊猫侯佩3 天前
WWDC26 全网首发:SwiftUI 8 “可重排序“操作符深度解析
ios·swiftui·swift
人月神话-Lee3 天前
【图像处理】颜色空间——RGB之外的世界
图像处理·人工智能·ios·ai编程·swift·rgb·颜色空间
东坡肘子3 天前
WWDC 2026 初印象:符合预期,但更务实 -- 肘子的 Swift 周报 #139
人工智能·swiftui·swift
大熊猫侯佩3 天前
WWDC26 前瞻:苹果可能放出的“王炸”,不只是 Siri
xcode·swift·wwdc