Ios 具有CoreData和NSFetchedResultsController的MVVM

Ios 具有CoreData和NSFetchedResultsController的MVVM,ios,swift,uitableview,core-data,mvvm,Ios,Swift,Uitableview,Core Data,Mvvm,我正在尝试将MVVM与Swift一起使用。我有一个使用NSFetchedResultsController的UITableViewController,还有一个在CoreData数据库中添加/编辑数据的后台进程 我不知道在哪里处理fetchedResultsControllerDelegate方法。viewModel还是viewController 因为在更新CoreData中的数据时,UITableView会自动更新,所以在我看来,在每个NSManagedObject实例周围创建一个包装器视图

我正在尝试将MVVM与Swift一起使用。我有一个使用NSFetchedResultsController的UITableViewController,还有一个在CoreData数据库中添加/编辑数据的后台进程

我不知道在哪里处理fetchedResultsControllerDelegate方法。viewModel还是viewController

因为在更新CoreData中的数据时,UITableView会自动更新,所以在我看来,在每个NSManagedObject实例周围创建一个包装器视图模型对象,并将其绑定到表视图不是正确的方法。这种方式有点违背了使用NSFetchedResultsController的目的

下面是我正在使用的简单的MVC代码,MVVM实现这一点的最佳方法是什么

//数据模型
@objc(员额)
公共类职位:NSManagedObject{
@NSManaged公共变量用户名:字符串?
@NSManaged public var createdAt:日期?
@nonobjc公共类func fetchRequest()->NSFetchRequest{
返回NSFetchRequest(entityName:“Post”)
}
}
//视图控制器
类ViewController:UIViewController、UITableViewDelegate、UITableViewDataSource、NSFetchedResultsControllerDelegate{
var fetchedResultsController:NSFetchedResultsController
变量tableView:UITableView={
返回UITableView()
}()
重写func viewDidLoad(){
view.addSubview(tableView)
tableView.delegate=self
tableView.dataSource=self
let fetchRequest:NSFetchRequest=Post.fetchRequest()
fetchRequest.predicate=NSPredicate(格式:“foo=%@和bar=%@”,参数数组:[“baz”,“loreum”])
fetchRequest.sortDescriptors=[NSSortDescriptor(键:#键路径(Post.createdAt),升序:false]
self.fetchedResultController=NSFetchedResultsController(fetchRequest:fetchRequest,
managedObjectContext:context,
sectionNameKeyPath:#keyPath(Post.createdAt),
缓存名称:nil)
self.fetchedResultController.delegate=self
做{
尝试self.fetchedResultController.performFetch()
}将let错误捕获为NSError{
fatalError(“错误:\(Error.localizedDescription)”)
}
}
//标记-NSFetchedResultsControllerDelegate
func controllerWillChangeContent(controller:NSFetchedResultsController){/**do stuff**/}
func controllerDidChangeContent(controller:NSFetchedResultsController){/**do stuff**/}
func控制器(控制器:NSFetchedResultsController,didChange sectionInfo:NSFetchedResultsSectionInfo,atSectionIndex sectionIndex:Int,类型:NSFetchedResultsChangeType){
开关类型{
案例.插入:
tableView.insertSections(IndexSet(整数:sectionIndex),带:。左)
案例.删除:
tableView.deleteSections(IndexSet(整数:sectionIndex),带:.fade)
违约:
打破
}
}
func控制器(controller:NSFetchedResultsController,didChange anObject:Any,在indexPath:indexPath?处,对于类型:NSFetchedResultsChangeType,newIndexPath:indexPath?{/**do stuff**/}
//标记-UITableViewDataSource
func numberOfSections(在tableView:UITableView中)->Int{
返回fetchedResultsController.sections?.count±0
}
func tableView(tableView:UITableView,numberofrowsinssection:Int)->Int{/**do stuff**/}
func tableView(tableView:UITableView,cellForRowAt indexath:indexPath)->UITableViewCell{
让cell=tableView.dequeueReusableCell(带有标识符:“我的单元格标识符”,for:indexath)作为!CustomUITableViewCell
configureCell(单元格,indexPath:indexPath)
返回单元
}
//标记单元配置
func configureCell(cell:CustomUITableViewCell,indexPath:indexPath){
设o=fetchedResultsController.object(位于:indexPath)
cell.textlab?.text=o.userName
//在此处配置更多单元格
}
}

您可以让它更具MVVM,但为什么要这样做呢?如果你只是在寻找更明确的分离,好吧,但请记住,你将不得不做大量的讨好。要回答原始问题,您需要创建一个新对象(必须是object,因为它将是FRC委托),该对象也是表视图数据源/委托。此新视图模型将订阅FRC更改相关的委托方法,将托管对象转换为单元格视图模型等,然后向视图控制器发送信号以重新加载数据等,或者您可以创建稍微转换的委托函数(或闭包)供视图控制器订阅,以便它知道何时重新加载表视图并查询视图模型中的计数、索引处的对象等


但事实上,这已经是FRC的功能,与之对抗似乎是不必要的工作,而且可能更容易出错。

苹果本身有时使用了一种非常好的
数据提供者
模式,我非常喜欢这种模式

您可以在此处找到一个示例:

此模式将FetchedResultsController分解为一个提供程序类,您可以将Viewcontroller(使用
弱var
)作为FetchedResultsController委托传递给此提供程序类


我希望您会发现这一点很有帮助。

在我看来,没有简单的方法可以将MVVM与CoreData结合起来。如果必须,ViewModel可以管理FRC,同时提供对映射到FRC委托方法的ViewController的回调。另一方面,如果您使用Realm,我发现它比CoreData更适合MVVM。但我不确定你的要求是什么。
// View controller
class ViewController :  UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate {
  var fetchedResultsController: NSFetchedResultsController<Post>
  var tableView:UITableView = {
    return UITableView()
  }()

  override func viewDidLoad(){
    view.addSubview(tableView)
    tableView.delegate = self
    tableView.dataSource = self

    let fetchRequest:NSFetchRequest<Post> = Post.fetchRequest()
    fetchRequest.predicate = NSPredicate(format: "foo = %@ and bar = %@", argumentArray: ["baz", "loreum"])
    fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(Post.createdAt), ascending: false)]

    self.fetchedResultController = NSFetchedResultsController(fetchRequest: fetchRequest,
                                                             managedObjectContext: context,
                                                             sectionNameKeyPath: #keyPath(Post.createdAt),
                                                             cacheName: nil)
    self.fetchedResultController.delegate = self

    do {
      try self.fetchedResultController.performFetch()
    } catch let error as NSError {
      fatalError("Error: \(error.localizedDescription)")
    }
  }

  // MARK - NSFetchedResultsControllerDelegate
  func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { /** do stuff **/ }
  func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { /** do stuff **/ }
  func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
    switch type {
    case .insert:
      tableView.insertSections(IndexSet(integer: sectionIndex), with: .left)
    case .delete:
      tableView.deleteSections(IndexSet(integer: sectionIndex), with: .fade)
    default:
      break;
    }
  }
  func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { /** do stuff **/ }

  // MARK - UITableViewDataSource
  func numberOfSections(in tableView: UITableView) -> Int {
    return fetchedResultsController.sections?.count ?? 0
  }
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { /** do stuff **/ }
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "my-cell-identifier", for: indexPath) as! CustomUITableViewCell
    configureCell(cell, indexPath: indexPath)
    return cell
  }

  // MARK - Cell configuration
  func configureCell(_ cell:CustomUITableViewCell, indexPath:IndexPath){
    let o = fetchedResultsController.object(at: indexPath)
    cell.textLabel?.text = o.userName
    // configure cell more here
  }
}