为何Core Data的container.performBackgroundTask()后tableView.reloadData()未触发?
Hey there, let's work through why your tableView isn't reloading after adding those test Core Data records! Here's what's going wrong and how to fix it step by step:
First, let's break down the core issues:
- The
performBackgroundTaskcreates a separate private context—you're not saving this context, so your new records never get written to the persistent store. - Your
NSFetchedResultsController(FRC) isn't properly initialized or connected to the main context, so it can't detect when new data is added. - You're relying on manual
tableView.reloadData()instead of using the FRC delegate methods, which are designed to handle Core Data changes automatically.
Step 1: Save the background context
When you add records in the background task context, you must save it to persist changes to the store. Otherwise, the main context (which your tableView uses) will never see the new data.
Update your background task code:
container?.performBackgroundTask { context in // Add your 100 test records here, example: for i in 0..<100 { let student = Student(context: context) student.name = "Test Student \(i)" // Set other Student properties as needed } // Save the background context to persist changes do { try context.save() print("Test records saved successfully!") } catch { print("Failed to save test records: \(error.localizedDescription)") } }
Step 2: Properly initialize your NSFetchedResultsController
You need to set up the FRC to use the main view context, assign its delegate, and run the initial fetch. Add this setup code to your viewDidLoad:
override func viewDidLoad() { super.viewDidLoad() // Initialize and configure the FRC first setupFetchedResultsController() // Now run your background data insertion task container?.performBackgroundTask { context in // ... (add test records + save code from Step 1) } } private func setupFetchedResultsController() { guard let mainContext = container?.viewContext else { print("Could not access main view context") return } // Create a fetch request for your Student entity let fetchRequest: NSFetchRequest<Student> = Student.fetchRequest() // FRC requires at least one sort descriptor—adjust to match your entity's properties fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)] // Initialize the FRC with the main context frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: mainContext, sectionNameKeyPath: nil, cacheName: nil) // Assign self as the delegate to receive change notifications frc?.delegate = self // Run the initial fetch to load existing data do { try frc?.performFetch() } catch { print("Failed to perform initial fetch: \(error.localizedDescription)") } }
Step 3: Implement FRC Delegate Methods
Instead of calling tableView.reloadData() manually, use the FRC delegate methods to handle updates with smooth animations. Add these to your MyTVC class:
// MARK: - NSFetchedResultsControllerDelegate func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { tableView.beginUpdates() } func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) { switch type { case .insert: tableView.insertSections(IndexSet(integer: sectionIndex), with: .automatic) case .delete: tableView.deleteSections(IndexSet(integer: sectionIndex), with: .automatic) default: break } } func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { switch type { case .insert: if let newIndexPath = newIndexPath { tableView.insertRows(at: [newIndexPath], with: .automatic) } case .delete: if let indexPath = indexPath { tableView.deleteRows(at: [indexPath], with: .automatic) } case .update: if let indexPath = indexPath { // Update your cell's content here let student = frc?.object(at: indexPath) if let cell = tableView.cellForRow(at: indexPath) { cell.textLabel?.text = student?.name // Update other cell elements as needed } } case .move: if let indexPath = indexPath, let newIndexPath = newIndexPath { tableView.deleteRows(at: [indexPath], with: .automatic) tableView.insertRows(at: [newIndexPath], with: .automatic) } @unknown default: break } } func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { tableView.endUpdates() }
Why this works:
- When you save the background context, Core Data automatically notifies the main context of changes.
- The FRC is listening to the main context, so it triggers its delegate methods when new data is merged in.
- The delegate methods handle incremental tableView updates (instead of a full reload) which is more efficient and provides better user experience with animations.
内容的提问来源于stack exchange,提问作者Ahmad

