当用户更改全局对象时重新初始化合并发布者

Posted

技术标签:

【中文标题】当用户更改全局对象时重新初始化合并发布者【英文标题】:Reinitialize Combine Publishers When User Changes Global Object 【发布时间】:2021-02-18 04:17:42 【问题描述】:

到目前为止,我有一个运行良好的 Core Data 发布者。我有一个Workspace 实体和一个Project 实体。我使用以下发布者来获取给定工作区的所有项目:

class ProjectModel: ObservableObject 
  @Published var projects = [Project]()
  private var cancellableSet: Set<AnyCancellable> = []

  init()
    CoreDataPublisher(request: Project.getAllProjects(), context: PersistenceController.shared.container.viewContext)
      .sink(
        receiveCompletion:  print($0) ,
        receiveValue:  [weak self] items in
          self?.projects = items
        )
      .store(in: &cancellableSet)
  

提取请求 getAllProjects() 位于此处的核心数据实体扩展中,其中 NSPredicate 基于 UI 中设置的 Workspace 对象进行过滤。

//Core Data Entity Extension
extension Project
  @nonobjc public class func getAllProjects() -> NSFetchRequest<Project> 
    let workspace = AppState.shared.workspace as Workspace //<-- The user can change this workspace
  
    let request = NSFetchRequest<Project>(entityName: "\(Self.self)")
    request.sortDescriptors = [NSSortDescriptor(keyPath: \Project.name, ascending: true)]
    request.predicate = NSPredicate(format: "workspace = %@", workspace)
    return request
  

这个Workspace 对象在一个全局状态类中:

class AppState: ObservableObject
  static let shared = AppState()
  @Published var workspace: Workspace!

  init()
    //Setup the workspace for the first time
  

我可以成功接收来自我的发布者的数据,并且可以成功更改 UI 中的全局 Workspace。问题是在更改Workspace后,发布者仍然指向旧的Workspace,最初在创建获取请求时设置。

AppStateworkspace发生变化时,如何提示ProjectModel重新初始化以更新发布者的状态?

【问题讨论】:

【参考方案1】:

实现这一点的典型方法是在您的发布者上使用flatMap 运算符。 flatMap 让您“根据收到的值创建一个新的发布者,然后将该发布者的输出用作整个发布者链的输出”。

看起来像这样:

AppState.shared.$workspace.flatMap  workspace in
    let request = NSFetchRequest<Project>(entityName: "\(Project.self)")
    request.sortDescriptors = [NSSortDescriptor(keyPath: \Project.name, ascending: true)]
    request.predicate = NSPredicate(format: "workspace = %@", workspace)
    return CoreDataPublisher(request: request, context: PersistenceController.shared.container.viewContext)

这为您提供了一个新的发布者:

当您的应用状态的.workspace 属性发生变化时,根据新的Workspace 值构造一个新的CoreDataPublisher 使用 CoreDataPublisher 作为整个发布者流的值的来源

【讨论】:

【参考方案2】:

在您的ProjectModel 中,我会从AnyCancellableSet 切换到特定的,以便您可以取消它:

var cdPublisherCancellable : AnyCancellable?

我会将此发布者的设置移出 init,因为您需要再次调用它:

func setupPublisher() 
  cdPublisherCancellable?.cancel()
  cdPublisherCancellable = CoreDataPublisher(request:)...

然后,由于工作区是共享 AppState 上的已发布属性,我将设置另一个发布者链接来观看它:

var workspaceCancellable : AnyCancellable?
init() 
  workspaceCancellable = AppState.shared.$workspace.sink  workspace in
    setupPublisher()
  

【讨论】:

这真的很有帮助,谢谢。这与戴夫的回答基本相同(据我所知)。 顺便问一下,您确定在覆盖下一行相同的可取消之前必须调用cdPublisherCancellable?.cancel() 吗?这不会有效地取代它吗? 我认为你是对的,出版商应该取消他们自己的 deinit -- 我已经预先准备好对这类事情非常谨慎(尤其是在测试期间)

以上是关于当用户更改全局对象时重新初始化合并发布者的主要内容,如果未能解决你的问题,请参考以下文章

重新绑定 UDP 套接字

当用户更新或重新安装应用程序时,注册 ID 是不是会更改

Makefile 只重新编译更改的对象?

登录后在哪里以及如何更改会话用户对象?

Vuex 全局状态更改不会触发 Nuxt 中 v-for 循环中的重新渲染组件

Vue 列表项不会在状态更改时重新渲染