将 AppKit/UIKit 中的键值观察转换为 Combine 和 SwiftUI

Converting Key-Value Observation in AppKit/UIKit to Combine and SwiftUI

我无法理解如何在 SwiftUI 中使用 Combine。我习惯于在 AppKit 和 UIKit 中使用键值观察,因为视图控制器不需要相互了解,只需对一些有助于确定状态的全局对象做出反应。

例如,在 AppKit/UIKit 应用程序中,我会创建一个全局状态对象,如下所示:

//Global State file
@objc class AppState: NSObject {
  @objc dynamic var project: Project?
}

//Create an instance I can access anywhere in my app
let app = AppState()

然后在视图控制器中,我可以收到有关我的应用程序范围的项目实例的任何更改的通知并做出相应的反应:

//View Controller
class MyViewController: NSViewController{
  var observerProject: NSKeyValueObservation?

  override func viewDidLoad() {
    observerProject = app.observe(\.project) { object, change in
      self.refreshData()
    }
  }
  
  func refreshData(){
    //Query my persistent store and update my UI
  }
}

Combine/SwiftUI 与此类似的是什么?

我是否需要创建一个 Publisher 然后监听我的全局对象变化?如果是这样,我如何使我的核心数据 @FetchRequest(其谓词包括我的全局 Project 对象)实时响应?

我用老方法做事已经很长时间了,以至于我对 SwiftUI/Combine 的转变感到相当困惑。

@FetchRequest 不适用于动态谓词(SO 中有一些解决方法),您将不得不为此使用“老派”NSFetchedResultsController 并将其放入 ObservableObject。这是一个带有示例的 video。这是很多设置和代码。

import SwiftUI
import Combine
class AppState: ObservableObject {
    static let shared = AppState()
    @Published var project: String?
    //Just to mimic updates
    var count = 0
    private init() {
        //Mimic getting updates to project
        Timer.scheduledTimer(withTimeInterval: 2, repeats: true){ timer in
            print("Timer")
            self.project = self.count.description
            self.count += 1
            if self.count >= 15{
                timer.invalidate()
            }
        }
    }
}
class MyViewModel: ObservableObject{
    @Published var refreshedData: String = "init"
    let appState: AppState = AppState.shared
    var projectCancellable: AnyCancellable?
    init() {
        
        projectCancellable = appState.$project.sink(receiveValue: {
            value in
            print(value ?? "nil")
            self.refreshData()
        })
    }
        func refreshData() {
        refreshedData = Int.random(in: 0...100).description
    }
}
struct MyView: View {
    @StateObject var vm: MyViewModel = MyViewModel()
    var body: some View {
        VStack{
            Text(vm.refreshedData)
        }
    }
}

struct MyView_Previews: PreviewProvider {
    static var previews: some View {
        MyView()
    }
}