title: NSFetchedResultsControllerDelegate implementation results in UI lag when adding many objects
body: <p>I have a sample app that has a single <code>NSManagedObject</code> (let's call the class <code>CustomObject</code>) with a single property.</p>
  
  <p>The app has a simple UI consisting of a view controller with a <code>UICollectionView</code> using an <code>NSFetchedResultsController</code> as the underlying data source.  The <code>NSFetchedResultsController</code> is also setup to use the view controller as it's <code>NSFetchedResultsControllerDelegate</code> delegate.  Additionally I have toolbar with some buttons to add/remove <code>CustomObject</code> items.</p>
  
  <p>One button will result in 10,000 <code>CustomObject</code> entities being created.  I'd like to be able to press this button, and once the entities are saved to the disk/ready to be used, the <code>UICollectionView</code> should update to reflect the new values.  I am 100% aware that there will be a delay of some kind - saving values like this is not instantaneous - I want the delay to occur on a background thread to prevent the UI from locking up!</p>
  
  <p>Despite me setting things up correctly from what I can tell, I experience UI lag whenever I implement any of the three "important" <code>NSFetchedResultsControllerDelegate</code> methods.  The UI lag duration varies based on the number of <code>CustomObject</code>s in the data store already.  For example if I just added the first 10,000, there is only a small amount of lag, less than 1 second.  If I've added 50,000 <code>CustomObject</code>s already the lag is much more noticeable and lasts for over 5-10 seconds in some cases.</p>
  
  <p>As I said, the UI lag only shows up when I implement either the <code>controllerWillChangeContent</code>, <code>controller:didChangeObject:atIndexPath:forChangeType:newIndexPath</code>, or <code>controllerDidChangeContent</code> methods.  Even with no actual implementation in the method just the fact that it is in the view controller makes the insertion slow.</p>
  
  <p>Any idea what I am doing wrong?  Is there a way around this?  Here's some code:</p>
  
  <p>CoreDataController.m - responsible for setting up the core data stack</p>
  
  <pre><code>//
  //  CoreDataController.m
  //  CoreDataTestApp
  //
  //  Created by ZOlbrys on 4/12/18.
  //  Copyright © 2018 ZOlbrys. All rights reserved.
  //
  
  #import "CoreDataController.h"
  #import &lt;UIKit/UIKit.h&gt;
  
  @interface CoreDataController()
  
  @property (nonatomic, strong) NSURL *persistentStoreURL;
  @property (nonatomic, strong) NSURL *managedObjectModelURL;
  
  @property (nonatomic, strong, readwrite) NSPersistentStoreCoordinator *persistentStoreCoordinator;
  @property (nonatomic, strong) NSManagedObjectModel *managedObjectModel;
  @property (nonatomic, strong, readwrite) NSManagedObjectContext *managedObjectContext;
  
  @end
  
  @implementation CoreDataController
  
  + (CoreDataController *)sharedController {
      static CoreDataController *sharedController;
  
      static dispatch_once_t onceToken;
  
      dispatch_once(&amp;onceToken, ^{
          NSURL *managedObjectModelURL = [NSURL fileURLWithPath:[[NSBundle bundleForClass:[self class]] pathForResource:@"CoreDataTestApp" ofType:@"momd"]];
  
          NSString *applicationDocumentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
          NSURL *persistentStoreURL = [NSURL fileURLWithPath:[applicationDocumentsDirectory stringByAppendingPathComponent:@"CoreDataTestApp.sqlite"]];
  
          sharedController = [[CoreDataController alloc] initWithManagedObjectModelURL:managedObjectModelURL persistentStoreURL:persistentStoreURL];
      });
  
      return sharedController;
  }
  
  - (instancetype)initWithManagedObjectModelURL:(NSURL*)managedObjectModelURL persistentStoreURL:(NSURL*)persistentStoreURL {
      self = [super init];
  
      if (self) {
          self.persistentStoreURL = persistentStoreURL;
          self.managedObjectModelURL = managedObjectModelURL;
  
          [self setupCoreDataStack];
      }
  
      return self;
  }
  
  - (void)setupCoreDataStack {
      // setup managed object model
      _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:self.managedObjectModelURL];
  
      // setup persistent store coordinator
      _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
      [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:self.persistentStoreURL options:nil error:nil];
  
      // setup MOC
      _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
      [_managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
  }
  
  @end
  </code></pre>
  
  <p>ViewController.m - responsible for, well, view controller stuff!</p>
  
  <pre><code>//
  //  ViewController.m
  //  CoreDataTestApp
  //
  //  Created by ZOlbrys on 4/12/18.
  //  Copyright © 2018 ZOlbrys. All rights reserved.
  //
  
  #import "ViewController.h"
  #import "CustomCell.h"
  #import "CoreDataController.h"
  #import "CustomObject+CoreDataClass.h"
  
  static NSString *const CUSTOM_CELL_REUSE_IDENTIFIER = @"CUSTOM_CELL_REUSE_IDENTIFIER";
  
  @interface ViewController ()&lt;UICollectionViewDataSource, UICollectionViewDelegate, NSFetchedResultsControllerDelegate&gt;
  
  @property (strong, nonatomic) IBOutlet UICollectionView *collectionView;
  @property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
  
  @end
  
  @implementation ViewController
  
  - (void)viewDidLoad {
      [super viewDidLoad];
  
      self.collectionView.dataSource = self;
      self.collectionView.delegate = self;
  
      [self setupFetchedResultsController];
  }
  
  - (void)setupFetchedResultsController {
      NSFetchRequest&lt;CustomObject *&gt; *fetchRequest = [CustomObject fetchRequest];
      fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:@"identifier" ascending:YES] ];
  
      self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[CoreDataController sharedController].managedObjectContext sectionNameKeyPath:nil cacheName:nil];
  
      self.fetchedResultsController.delegate = self;
  
      [self.fetchedResultsController performFetch:nil];
  
      [self.collectionView reloadData];
  }
  
  - (void)addObjects:(NSUInteger)objectCount {
      NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
      temporaryContext.parentContext = [CoreDataController sharedController].managedObjectContext;
  
      [temporaryContext performBlock:^{
          for (int i = 0; i &lt; objectCount; i++) {
              CustomObject *object = [NSEntityDescription insertNewObjectForEntityForName:@"CustomObject" inManagedObjectContext:temporaryContext];
              object.identifier = [[NSUUID UUID] UUIDString];
          }
  
          NSError *error;
          if (![temporaryContext save:&amp;error]) {
              NSLog(@"Error: %@", error);
          }
  
          [[CoreDataController sharedController].managedObjectContext performBlock:^{
              NSError *error;
              if (![[CoreDataController sharedController].managedObjectContext save:&amp;error]) {
                  NSLog(@"Error: %@", error);
              }
          }];
      }];
  }
  
  - (void)removeObjects:(NSUInteger)objectCount {
      NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
      temporaryContext.parentContext = [CoreDataController sharedController].managedObjectContext;
  
      [temporaryContext performBlock:^{
          NSFetchRequest&lt;CustomObject *&gt; *fetchRequest = [CustomObject fetchRequest];
          fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:@"identifier" ascending:YES] ];
          fetchRequest.fetchLimit = objectCount;
  
          NSFetchedResultsController&lt;CustomObject *&gt; *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:temporaryContext sectionNameKeyPath:nil cacheName:nil];
  
          [fetchedResultsController performFetch:nil];
  
          for (CustomObject *object in fetchedResultsController.fetchedObjects) {
              [temporaryContext deleteObject:object];
          }
  
          NSError *error;
          if (![temporaryContext save:&amp;error]) {
              NSLog(@"Error: %@", error);
          }
  
          [[CoreDataController sharedController].managedObjectContext performBlock:^{
              NSError *error;
              if (![[CoreDataController sharedController].managedObjectContext save:&amp;error]) {
                  NSLog(@"Error: %@", error);
              }
          }];
      }];
  }
  
  - (IBAction)add10k:(id)sender {
      [self addObjects:10000];
  }
  
  - (IBAction)add1:(id)sender {
      [self addObjects:1];
  }
  
  - (IBAction)refetchData:(id)sender {
      [self.fetchedResultsController performFetch:nil];
  
      [self.collectionView reloadData];
  }
  
  - (IBAction)remove1:(id)sender {
      [self removeObjects:1];
  }
  
  - (IBAction)remove10k:(id)sender {
      [self removeObjects:10000];
  }
  
  #pragma mark UICollectionViewDataSource
  
  - (NSInteger)collectionView:(nonnull UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
      return [self.fetchedResultsController.sections objectAtIndex:section].numberOfObjects;
  }
  
  - (nonnull __kindof UICollectionViewCell *)collectionView:(nonnull UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath {
      CustomCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:CUSTOM_CELL_REUSE_IDENTIFIER forIndexPath:indexPath];
  
      [cell setDisplayText:@"TODO"];
  
      cell.backgroundColor = [UIColor blueColor];
  
      return cell;
  }
  
  #pragma mark NSFetchedResultsControllerDelegate
  
  //- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
  //    // TODO
  //}
  //
  //- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
  //    // TODO
  //}
  //
  //- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
  //    // TODO
  //}
  
  @end
  </code></pre>
  
  <p>Of course, I did some profiling - with <code>controllerWillChangeContent</code> uncommented above, I see this:</p>
  
  <p><a href="https://i.stack.imgur.com/jTWro.jpg" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/jTWro.jpg" alt="enter image description here"></a></p>
  
  <p>Which is not my code - it's internal Apple code...</p>
  
  <p>Here's a github link to a project showing this issue:</p>
  
  <p><a href="https://github.com/ZOlbrys/CoreDataTestApp" rel="nofollow noreferrer">Sample Core Data project</a></p>