@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 忽略观察,再挂包装器" ------ 问题秒解。

相关推荐
初级代码游戏1 天前
iOS开发 SwiftUI 14:ScrollView 滚动视图
ios·swiftui·swift
初级代码游戏1 天前
iOS开发 SwitftUI 13:提示、弹窗、上下文菜单
ios·swiftui·swift·弹窗·消息框
zhyongrui1 天前
托盘删除手势与引导体验修复:滚动冲突、画布消失动画、气泡边框
ios·性能优化·swiftui·swift
zhangfeng11331 天前
CSDN星图 支持大模型微调 trl axolotl Unsloth 趋动云 LLaMA-Factory Unsloth ms-swift 模型训练
服务器·人工智能·swift
zhyongrui2 天前
SnipTrip 发热优化实战:从 60Hz 到 30Hz 的性能之旅
ios·swiftui·swift
大熊猫侯佩3 天前
Neo-Cupertino 档案:撕开 Actor 的伪装,回归 Non-Sendable 的暴力美学
swift·observable·actor·concurrency·sendable·nonsendable·data race
2501_915921434 天前
在没有源码的前提下,怎么对 Swift 做混淆,IPA 混淆
android·开发语言·ios·小程序·uni-app·iphone·swift
00后程序员张5 天前
对比 Ipa Guard 与 Swift Shield 在 iOS 应用安全处理中的使用差异
android·开发语言·ios·小程序·uni-app·iphone·swift
大熊猫侯佩5 天前
星际穿越:SwiftUI 如何让 ForEach 遍历异构数据(Heterogeneous)集合
swiftui·swift·遍历·foreach·any·异构集合·heterogeneous
hjs_deeplearning5 天前
认知篇#15:ms-swift微调中gradient_accumulation_steps和warmup_ratio等参数的意义与设置
开发语言·人工智能·机器学习·swift·vlm