如何在 NSFetchedResultsController 中切换不同的 CoreData 实体?

How to switch between different CoreData Entities in NSFetchedResultsController?

我在我的项目中使用 CoreData。它有 2 个独立的 EntitiesCurrencyCurrency2。这些实体具有相同的属性,但存储填充 tableView 的不同数据。 用户可以通过在应用程序设置中选择选项来选择他想在tableView中看到的数据(我可以通过将其保存在UserDefaults中来存储他的选择) .

这里的问题是如何用不同的 Entity 数据填充 tableView?我不能只是将这里的字符串文字中的名称从 Currency 更改为 Currency2:

private var fetchedResultsController: NSFetchedResultsController<Currency>!

它只会给我一个错误。所以我想我应该再创建一个 fetchedResultsController...:[=​​28=]

private var fetchedResultsController: NSFetchedResultsController<Currency2>!

但是我应该将下面的所有代码加倍,因为我需要在它们之间切换。如果将来我需要在 3 或 4 个不同的 Entities 之间切换怎么办?

如何使代码可重用,同时通过 NSFetchedResultsController 接收所需的切换结果?

目前我的 NSFetchedResultsController 设置如下:

class CurrencyViewController: UIViewController {

  private var fetchedResultsController: NSFetchedResultsController<Currency>!

   override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    setupFetchedResultsController()
   }

    //MARK: - TableView DataSource Methods

     func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return fetchedResultsController.sections![section].numberOfObjects
     }


     func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
           let cell = tableView.dequeueReusableCell(withIdentifier: "currencyCell", for: indexPath) as! CurrencyTableViewCell
           let currency = fetchedResultsController.object(at: indexPath)
    
           cell.shortName.text = currency.shortName
           cell.fullName.text = currency.fullName

           return cell
     }

   //MARK: - NSFetchedResultsController Setup & Delegates

      func setupFetchedResultsController(with searchPredicate: NSPredicate? = nil) {
           let predicate = NSPredicate(format: "isForCurrencyScreen == YES")
        
           var sortDescriptor: NSSortDescriptor {
            if pickedSection == "По имени" {
                return NSSortDescriptor(key: "fullName", ascending: sortingOrder)
            } else {
                return NSSortDescriptor(key: "shortName", ascending: sortingOrder)
            }

          fetchedResultsController = coreDataManager.createCurrencyFetchedResultsController(with: predicate, and: sortDescriptor)
          fetchedResultsController.delegate = self
          try? fetchedResultsController.performFetch()
          tableView.reloadData()
     }

      func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
          tableView.beginUpdates()
      }

      func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
          tableView.endUpdates()
      }
}

这也是我的coreDataManager.createCurrencyFetchedResultsController方法:

  private let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

  func createCurrencyFetchedResultsController(with predicate: NSPredicate? = nil, and sortDescriptor: NSSortDescriptor? = nil) -> NSFetchedResultsController<Currency> {
        let request: NSFetchRequest<Currency> = Currency.fetchRequest()
        let baseSortDescriptor = NSSortDescriptor(key: "shortName", ascending: true)
        request.predicate = predicate
        
        return NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
    }

如果您的实体始终具有相同的一组属性并且没有本质上的不同,请考虑使它们成为一个实体并通过添加额外的属性来区分它们。这将使系统可以无限扩展,而无需编写太多额外的代码,代码也会简单得多。

如果您真的需要它们是独立的实体,我建议如下:

  1. 创建一个新的基础实体,作为两个实体的父实体 class。我们称它为 BaseCurrency.
  2. 将其指定为 CurrencyCurrency2 的父实体。
  3. BaseCurrency 中创建共享属性并将它们从子实体中移除。结果将如下所示:

  1. 为任何实体创建一个 returns 一个 NSFetchedResultsController 的通用工厂方法:
enum SortingField {
    case name
    case symbol
}

private func makeFetchedResultsController<T: BaseCurrency>(
    context: NSManagedObjectContext,
    sortBy: SortingField
) -> NSFetchedResultsController<T> {
    let request: NSFetchRequest<T> = NSFetchRequest(entityName: T.description())

    let sortDescriptor: NSSortDescriptor = {
        switch sortBy {
        case .name:
            return NSSortDescriptor(keyPath: \T.name, ascending: true)
        case .symbol:
            return NSSortDescriptor(keyPath: \T.symbol, ascending: true)
        }
    }()
    request.sortDescriptors = [sortDescriptor]

    return NSFetchedResultsController(
        fetchRequest: request,
        managedObjectContext: context,
        sectionNameKeyPath: nil,
        cacheName: String(describing: T.self)
    )
}

请注意,此方法不接受任何排序描述符或谓词,而是接受枚举。它允许从具体的字段名称中抽象出来,并能够在方法内部使用泛型类型的关键路径。如果您需要更高级的 sorting/filtering 功能,可以通过引入描述过滤器和排序的更复杂的数据结构来构建这个想法。

  1. 当需要为特定实体创建控制器时,调用工厂方法:
let frc: NSFetchedResultsController<Currency2> =
    makeFetchedResultsController(
        context: context,
        sortBy: .symbol
    )

当然你必须在这里明确指定控制器的类型。

这只是一个基本的有限示例,但希望您可以将其用作基础并在此基础上进行构建。