You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中Observable对象内结构体属性无法触发视图刷新的问询

SwiftUI @Observable 与值类型属性的视图更新问题

我在探索SwiftUI的@Observable特性,尝试通过Environment实现依赖注入时,搭建了以下代码结构:

初始代码实现

状态与数据仓库定义

@Observable
class AppState {
    var dataRepository = DataRepository()
}

struct DataRepository {
    private(set) var isLoading = false
    
    mutating func downloadData() async -> String {
        isLoading = true

        try? await Task.sleep(for: .seconds(3))

        isLoading = false
        return "Some data"
    }
}

应用与视图注入

@main
struct DependencyInjectionApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .environment(AppState())
    }
}

struct ContentView: View {
    @Environment(AppState.self) private var appState
    
    @State private var name: String = ""
    
    var body: some View {
        Group {
            if appState.dataRepository.isLoading {
                ProgressView()
            } else {
                Text(name)
            }
        }
        .padding()
        .task {
            name = await appState.dataRepository.downloadData()
        }
    }
}

#Preview {
    ContentView()
        .environment(AppState())
}

运行现象

  • 变量name最终成功更新为"Some data"
  • 加载过程中未显示ProgressView

问题根源

  • 下载完成后,name的@State属性更新触发了视图重绘
  • isLoading是Observable类AppState内部值类型DataRepository的属性,无法触发视图重绘

疑问解答

这属于@Observable的特性限制,你的理解存在偏差:

  • @Observable只会追踪类本身的属性赋值操作,当属性是值类型时,只有对该属性的整体赋值(比如appState.dataRepository = DataRepository())才会被感知到。
  • 值类型内部的属性变更(比如dataRepository.isLoading = true)属于值类型的内部修改,不会触发外部Observable类的属性赋值事件,因此SwiftUI无法捕获到这个变化,也就不会触发视图重绘。

可行调整方案

  • 方案1:将isLoading移至AppState
    把状态直接放在Observable类中,利用@Observable的特性触发重绘:

    @Observable
    class AppState {
        var isLoading = false
        var dataRepository = DataRepository()
        
        func downloadData() async -> String {
            isLoading = true
            let result = await dataRepository.downloadData()
            isLoading = false
            return result
        }
    }
    
    struct DataRepository {
        func downloadData() async -> String {
            try? await Task.sleep(for: .seconds(3))
            return "Some data"
        }
    }
    
  • 方案2:将DataRepository改为@Observable类
    让数据仓库本身成为Observable类,其内部属性变更会被SwiftUI追踪:

    @Observable
    class AppState {
        var dataRepository = DataRepository()
    }
    
    @Observable
    class DataRepository {
      private(set) var isLoading = false
      
      func downloadData() async -> String {
          isLoading = true
    
          try? await Task.sleep(for: .seconds(3))
    
          isLoading = false
          return "Some data"
      }
    }
    

内容的提问来源于stack exchange,提问作者Adam Carter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 14:23:59