在 Clean Architecture 中使用 NSFetchedResultsController

ad-*_*son 5 core-data ios swift viper-architecture clean-architecture

我已经在没有太多运气的情况下寻找了答案。这个问题几乎相同,但答案不是很清楚(至少对我来说!): 它是 VIPER 架构中 NSFetchedResultsController 的位置?

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

ad-*_*son 4

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

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

我发现使用 DataSource Delegate 作为 ViewController 更容易且更一致(当表视图是实现的一部分时) - 我将其实现为 ViewController 的单独类。

这种方法维护了标准的 VIP 周期,并且不需要视图层中的模型知识。

处理委托调用有点棘手。NFRC 通常与视图层绑定来处理表视图数据委托请求: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)
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

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

因此,它将所有事件传递给演示者,演示者再传递给可以处理这些事件的数据源委托。

但这还不够。为了提高效率,数据源委托确实需要与 NSFRC 交互,以将表视图行映射到正确索引路径处的数据行,处理部分信息等。因此,我所做的是创建一个名为 DynamicDataSource 的协议和一个实现由 Interactor 初始化以“包装”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
}
Run Code Online (Sandbox Code Playgroud)

如果持久性存储更改为内存存储或 JSON 层,则动态数据源实现可以适当处理该问题,而不会影响视图。显然,这是使用 NFRC 的一种复杂方式,但我认为它是一个有用的类。对于一个简单的应用程序来说,这可能有点过分了。然而,它确实有效,而且我认为这是一个很好的、一致的妥协。

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