【问题标题】:Use of NSFetchedResultsController in Clean Architecture在 Clean Architecture 中使用 NSFetchedResultsController
【发布时间】:2018-01-03 20:12:12
【问题描述】:

我一直在寻找这个问题的答案,但运气不佳。这个问题几乎相同,但答案不是很清楚(至少对我来说!): Which it is the place for NSFetchedResultsController in VIPER architecture?

NSFetchedResultsController 似乎对于 iOS 应用程序来说是一种非常有用的方法,但我看到的所有示例都将它放在了 ViewController 层——至少,VC 成为了一个委托。在 Clean Architecture/Viper 中,模型层与视图层非常脱节,我无法弄清楚 NSFRC 是如何在这样的架构中使用的。上述问题的答案暗示交互者应该是一个代表,但这没有意义——托管对象随后将呈现给交互者,而不是 PONSO。也许我还不太了解它,但是(a)它是否在清洁架构中占有一席之地? (b) 如果是,那么是否需要正确的 Swift 实现模式?

【问题讨论】:

    标签: ios swift core-data clean-architecture viper-architecture


    【解决方案1】:

    也许 NSFetchedResultsController 在 iOS 项目中的数据与视图层之间建立了最紧密的耦合。为了解决这个问题,我在 NSFetchedResultsController 周围使用了一个包装器,其方式与 @ad-johnson 解释的类似。我还创建了一个 Xcode 模板,可以在项目中自动为我创建所需的协议。

    我认为我已经成功地通过边界将 NSFetchedResultsController 与应用程序的其他层完全分开,而不会丢失 NSFetchedResultController 例程功能。因此,我可以轻松地测试、修改甚至替换所有层,因为层之间存在松散耦合。

    如果您想了解更多信息,here in GitHub 是一个名为CarOwnership 的项目,它描述了如何在干净的架构中处理 NSFetchedResultsController。您也可以找到它here in my blog

    【讨论】:

      【解决方案2】:

      您可以将 NSFetchedResultController 视为控制器或适配器。

      我不同意 ad-johnson 的 NSFRC 属于模型。

      干净架构中的依赖规则说依赖必须指向内部。这并不是说用例或适配器应该不知道模型层。

      【讨论】:

        【解决方案3】:

        这就是我最后所做的。 NSFetchedResultsController (NFRC) 需要以两种方式处理 - 获取数据,即执行查询,以及通过委托调用通知 ManagedObject (MO) 集的更改。

        获取数据不会触发委托调用。因此,您通常会返回运行 fetch 的结果,即 anNFRC.fetchedObjects(),在 worker 或 interactor 中重新打包为 PONSOS,并将这些传递给 Presenter 以传递给 ViewController。

        我发现使用 DataSource Delegate 和 ViewController 一样更容易且符合规范(当 Table View 是实现的一部分时)——我将它作为 ViewController 的一个单独类来实现。

        这种方法保持标准的 VIP 循环,并且不需要视图层中的模型知识。

        处理委托调用有点棘手。 NFRC 通常与 View 层绑定以处理 Table View 数据委托请求:NFRC 通知插入、删除、移动、更新更改,并且委托适当地处理它。但是,在 VIP 架构中,由于 NFRC 无法附加到视图,因此无法发生 - 它位于模型层并需要留在那里。

        我在 Store 实例中对此进行了实例化,并将 Store 实例设为 NFRC 委托,并将委托方法实现为:

            func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
        print("item changed")
                guard let managedItem = anObject as? ManagedItem else {
                    return
                }
                let item = managedItem.toItem()
                var eventType: EventType
                switch type {
                case .insert:
                    eventType = EventType.insert
                case .delete:
                    eventType = EventType.delete
                case .move:
                    eventType = EventType.move
                case .update:
                    eventType = EventType.update
                }
        
                let itemChangeEvent = ItemChangeEvent(eventType: eventType, item: item, index: indexPath, newIndex: newIndexPath)
                results.append(itemChangeEvent)
            }
        
            func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
                results = []
                print ("Begin update")
            }
        
            func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
                print("End updates")
                if let completionHandler = completion {
                    completionHandler(results)
                }
            }
        

        基本上,我初始化一个空数组(开始更新),将所有通知作为事件对象(PO​​NSOS)整理到该数组(I,D,M,U)中,然后在完成时运行完成处理程序(结束更新)。完成处理程序作为 fetch() 操作的一部分传入并存储以供将来使用 - 即当需要通知 MO 更改时。 完成处理程序从 Interactor 传递过来,如下所示:

            func processFetchResults(itemChangeEvents: [ItemChangeEvent]) {
            let response = ListItems.FetchItems.Response(itemEvents: itemChangeEvents)
            presenter?.presentFetchedItems(response: response)
        }
        

        所以它将所有事件传递给 Presenter,Presenter 传递给可以处理它们的数据源委托。

        但这还不够。为了提高效率,数据源委托确实需要与 NSFRC 交互,以将表视图行映射到正确索引路径上的数据行,处理部分信息等。因此,我所做的是创建一个名为 DynamicDataSource 的协议和一个由交互器初始化以“包装” NSFRC 并代理其方法的实现。虽然模型在技术上被交给了视图层,但它实际上被包装在一个协议后面,并且实现将 MO 转换为 PONSOS。所以我认为它是 Presenter 层的扩展(不是 Swift 扩展!)。

            protocol ListItemsDynamicDataSource: AnyObject {
            // MARK: - Helper methods
            func numberOfSections() -> Int
            func rowsInSection(_ section: Int) -> Int
            func getItem(index: IndexPath) -> ListItems.FetchItems.ViewModel.DisplayedItem
        }
        

        如果将持久性存储更改为内存存储或 JSON 层,则动态数据源实现可以适当地处理该问题,而不会影响视图。显然这是使用 NFRC 的一种复杂方式,但我认为这是一个有用的类。对于一个简单的应用程序,这可能是矫枉过正。但是,它确实有效,而且我认为这是一个很好的、符合要求的折衷方案。

        值得补充的是,我对 Swift 和 IOS 开发还很陌生,所以这可能不是世界上最好的代码,可能还有更好的方法!我总是乐于接受反馈和改进建议。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-10-02
          • 2016-04-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-06-18
          • 1970-01-01
          相关资源
          最近更新 更多