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

SwiftUI中订阅@Published属性更新@State变量的优化方案

解决方案:利用Combine监听模型变更优化UI更新

首先修正代码里的拼写错误:MediaGroup里的thumbnaill应该是thumbnail,建议改为可选类型(UUID?),更符合“可能没有缩略图”的场景:

class MediaGroup: ObservableObject {
  @Published var thumbnail: UUID? 
}

核心思路

问题根源是task只在视图初始化时执行一次,无法捕获model.thumbnail的后续变更。我们可以利用Combine框架监听@Published属性的Publisher,在缩略图ID变化时自动触发图片重新加载,替代NotificationCenter的全局通知方案。

方案1:直接在视图中用.onReceive监听变更

这种方式不需要额外ViewModel,适合简单场景:

struct MediaItemView: View {
  @State private var loadedThumbnail: Image?
  @ObservedObject var model: MediaGroup
  let imageLoader: ImageLoader // 假设这是你的图片加载器实例

  var body: some View {
    ZStack {
      // 展示加载好的缩略图或占位图
      loadedThumbnail?
        .resizable()
        .scaledToFit()
      ?? Color.gray.opacity(0.3)
      
      // 其他媒体组信息(比如标题等)
      Text(model.name)
        .padding()
    }
    // 初始化时加载一次缩略图
    .task {
      guard let initialThumbID = model.thumbnail else { return }
      await loadThumbnail(for: initialThumbID)
    }
    // 监听thumbnail的变更,每次变化都重新加载
    .onReceive(model.$thumbnail) { newThumbnailID in
      guard let thumbID = newThumbnailID else {
        loadedThumbnail = nil
        return
      }
      Task {
        await loadThumbnail(for: thumbID)
      }
    }
  }

  // 封装加载逻辑,避免重复代码
  private func loadThumbnail(for mediaID: UUID) async {
    loadedThumbnail = nil // 先清空旧图,显示占位
    // 假设imageLoader有async版本的加载方法,没有的话可以用withCheckedContinuation封装
    loadedThumbnail = await imageLoader.loadAsync(mediaID)
  }
}

// 给你的ImageLoader扩展async加载方法(如果原来只有闭包版本)
extension ImageLoader {
  func loadAsync(_ mediaID: UUID) async -> Image? {
    await withCheckedContinuation { continuation in
      self.load(mediaID) { image in
        continuation.resume(returning: image)
      }
    }
  }
}

方案2:用ViewModel封装加载逻辑(更易维护)

如果视图逻辑复杂,建议把图片加载的响应式逻辑放到ViewModel中,让视图只负责展示:

class MediaItemViewModel: ObservableObject {
  @Published var loadedThumbnail: Image?
  private let model: MediaGroup
  private let imageLoader: ImageLoader
  private var cancellables = Set<AnyCancellable>()

  init(model: MediaGroup, imageLoader: ImageLoader) {
    self.model = model
    self.imageLoader = imageLoader
    
    // 监听model的thumbnail变更
    model.$thumbnail
      .sink { [weak self] thumbID in
        guard let self = self else { return }
        self.loadThumbnail(for: thumbID)
      }
      .store(in: &cancellables)
    
    // 初始化加载
    loadThumbnail(for: model.thumbnail)
  }

  private func loadThumbnail(for mediaID: UUID?) {
    loadedThumbnail = nil
    guard let mediaID = mediaID else { return }
    
    imageLoader.load(mediaID) { [weak self] image in
      DispatchQueue.main.async {
        self?.loadedThumbnail = image
      }
    }
  }
}

struct MediaItemView: View {
  @StateObject private var viewModel: MediaItemViewModel

  init(model: MediaGroup, imageLoader: ImageLoader) {
    _viewModel = StateObject(wrappedValue: MediaItemViewModel(model: model, imageLoader: imageLoader))
  }

  var body: some View {
    ZStack {
      viewModel.loadedThumbnail?
        .resizable()
        .scaledToFit()
      ?? Color.gray.opacity(0.3)
      
      Text(model.name)
        .padding()
    }
  }
}

关键优势

  • 基于Combine的响应式监听,直接绑定Model的变更,避免了NotificationCenter的全局耦合,代码更清晰。
  • 自动捕获model.thumbnail的所有变更,包括初始化和后续修改,确保UI实时同步。
  • 逻辑封装后,视图层职责更单一,便于后续维护和测试。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 18:14:56