SwiftUI+Combine - 动态订阅发布者的字典

Posted

技术标签:

【中文标题】SwiftUI+Combine - 动态订阅发布者的字典【英文标题】:SwiftUI+Combine - Dynamicaly subscribing to a dict of publishers 【发布时间】:2021-08-02 14:56:02 【问题描述】:

在我的项目中,我拥有大量通过 grpc 流更新的项目。在应用程序内部有几个地方我将这些项目呈现给 UI,我想传播实时更新。

简化代码:

struct Item: Identifiable 
    var id:String = UUID().uuidString
    var name:String
    var someKey:String
    
    init(name:String)
        self.name=name
    

class DataRepository 
    public var serverSymbols: [String: CurrentValueSubject<Item, Never>] = [:]
    
    // method that populates the dict
    func getServerSymbols(serverID:Int)
        someService.fetchServerSymbols(serverID: serverID) response in
            response.data.forEach  (name,sym) in
                self.serverSymbols[name] = CurrentValueSubject(Item(sym))
            
        
    

    // background stream that updates the values
    func serverStream(symbols:[String] = [])
        someService.initStream() update in
            DispatchQueue.main.async 
                self.serverSymbols[data.id]?.value.someKey = data.someKey
            
        
    
     

视图模型:

class SampleViewModel: ObservableObject 
    @Injected var repo:DataRepository   // injection via Resolver

    // hardcoded value here for simplicity (otherwise dynamically added/removed by user)
    @Published private(set) var favorites:[String] = ["item1","item2"]

    func getItem(item:String) -> Item          
        guard let item = repo.serverSymbols[item] else  return Item(name:"N/A")
        return ItemPublisher(item: item).data
    


class ItemPublisher: ObservableObject 
    @Published var data:Item = Item(name:"")
    private var cancellables = Set<AnyCancellable>()
    
    init(item:CurrentValueSubject<Item, Never>)
        item
            .receive(on: DispatchQueue.main)
            .assignNoRetain(to: \.data, on: self)
            .store(in: &cancellables)
    

带有子视图的主视图:

struct FavoritesView: View 
    @ObservedObject var viewModel: QuotesViewModel = Resolver.resolve()
    var body: some View 
        VStack 
            ForEach(viewModel.favorites, id: \.self)  item in
                    FavoriteCardView(item: viewModel.getItem(item: item))
            
        
    

struct FavoriteCardView: View 
    var item:Item
    var body: some View 
        VStack 
            Text(item.name)
            Text(item.someKey)   // dynamic value that should receive the updates
        
    

我肯定错过了一些东西,或者这是一个完全错误的方法,但是我的项目卡没有收到任何更新(我确认后端流处于活动状态并且 serverSymbols 字典正在更新)。任何建议将不胜感激!

【问题讨论】:

可能不是 only 问题,但您应该使用.send() 而不是self.serverSymbols[data.id]?.valueItemstruct 还是 class 项目是一个结构,我已经编辑了问题以添加结构 你能加入minimal reproducible example吗?这里不足以测试您的代码 这是一个相当复杂的设置,通过服务器端流进行后台线程更新,这就是为什么我只发布了简化的相关部分来说明问题。我会试着看看我是否可以把它拆下来足够 MRE 了。 这不起作用我并不感到惊讶。为什么您希望FavoriteCardView 中的item 会自动更新?如果您详细描述您期望发生的事情,将会非常有帮助。 【参考方案1】:

我意识到我犯了一个错误 - 为了接收更新,我需要传递 ItemPublisher 本身。 (我从我的 viewModel 的方法中错误地返回了 ItemPublisher.data)

我已经重构了代码并让 ItemPublisher 使用项目密钥直接从我的存储库中提供数据,所以现在每张卡都使用发布者单独订阅。

现在的最终工作代码:

class SampleViewModel: ObservableObject 
    // hardcoded value here for simplicity (otherwise dynamically added/removed by user)
    @Published private(set) var favorites:[String] = ["item1","item2"]


MainView 和 CardView:

struct FavoritesView: View 
    @ObservedObject var viewModel: QuotesViewModel = Resolver.resolve()
    var body: some View 
        VStack 
            ForEach(viewModel.favorites, id: \.self)  item in
                    FavoriteCardView(item)
            
        
    


struct FavoriteCardView: View 
    var itemName:String
    @ObservedObject var item:ItemPublisher
    
    init(_ itemName:String)
        self.itemName = itemName
        self.item = ItemPublisher(item:item)
    
    var body: some View 
        let itemData = item.data
        VStack 
            Text(itemData.name)
            Text(itemData.someKey)   
        
    

最后,修改了 ItemPublisher:

class ItemPublisher: ObservableObject 
    @Injected var repo:DataRepository
    @Published var data:Item = Item(name:"")
    private var cancellables = Set<AnyCancellable>()
    
    init(item:String)
        self.data = Item(name:item)
        if let item = repo.serverSymbols[item] 
            self.data = item.value
            item.receive(on: DispatchQueue.main)
                .assignNoRetain(to: \.data, on: self)
                .store(in: &cancellables)
               
    

【讨论】:

以上是关于SwiftUI+Combine - 动态订阅发布者的字典的主要内容,如果未能解决你的问题,请参考以下文章

异步下载图像时SwiftUI和Combine工作不顺畅

使用 SwiftUI 和 Combine 根据授权状态有条件地显示视图?

SwiftUI/Combine 发布者是双向的吗?

如何使用 Combine 框架和 SwiftUI 发布网络请求的数据

swiftui+combine:为啥滚动 LazyVGrid 时 isFavoriteO 改变了?

如何使用 Combine 的 CurrentValueSubject 并在 SwiftUI 视图中访问它?