ios - CoreData 子上下文、NSFetchedResultsController 和主线程

标签 ios objective-c multithreading core-data nsmanagedobjectcontext

正在关注 this excellent post通过 Olivier Drobnik,我实现了 CoreData 专家 Marcus S. Zarra 提出的三层 CoreData 堆栈:

Three-layer CoreData context architecture

与此图和我的代码的唯一区别是我只使用一个临时背景MOC,以避免在多个临时MOC中插入对象时出现重复。这是我的上下文初始化代码:

#pragma mark - NSManagedObjectContexts

+ (NSManagedObjectContext *)privateManagedObjectContext
{
    if (!_privateManagedObjectContext) {

        // Setup MOC attached to PSC
        _privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [_privateManagedObjectContext setPersistentStoreCoordinator:[self persistentStoreCoordinator]];

        // Add notification to perform save when the child is updated
        _privateContextSaveObserver =
        [[NSNotificationCenter defaultCenter]
         addObserverForName:NSManagedObjectContextDidSaveNotification
         object:nil
         queue:nil
         usingBlock:^(NSNotification *note) {
             NSManagedObjectContext *savedContext = [note object];
             if (savedContext.parentContext == _privateManagedObjectContext) {
                 [_privateManagedObjectContext performBlock:^{
                     NSLog(@"AMBCoreData -> saving privateMOC");
                     NSError *error;
                     if (![_privateManagedObjectContext save:&error]) {
                         NSLog(@"AMBCoreData -> error saving _privateMOC: %@ %@", [error localizedDescription], [error userInfo]);
                     }
                 }];
             }
         }];
    }
    return _privateManagedObjectContext;
}

+ (NSManagedObjectContext *)mainUIManagedObjectContext
{
    if (!_mainUIManagedObjectContext) {

        // Setup MOC attached to parent privateMOC in main queue
        _mainUIManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        [_mainUIManagedObjectContext setParentContext:[self privateManagedObjectContext]];

        // Add notification to perform save when the child is updated
        _mainUIContextSaveObserver =
        [[NSNotificationCenter defaultCenter]
         addObserverForName:NSManagedObjectContextDidSaveNotification
         object:nil
         queue:nil
         usingBlock:^(NSNotification *note) {
             NSManagedObjectContext *savedContext = [note object];
             if (savedContext.parentContext == _mainUIManagedObjectContext) {
                 NSLog(@"AMBCoreData -> saving mainUIMOC");
                 [_mainUIManagedObjectContext performBlock:^{
                     NSError *error;
                     if (![_mainUIManagedObjectContext save:&error]) {
                         NSLog(@"AMBCoreData -> error saving mainUIMOC: %@ %@", [error localizedDescription], [error userInfo]);
                     }
                 }];
             }
         }];
    }
    return _mainUIManagedObjectContext;
}

+ (NSManagedObjectContext *)importManagedObjectContext
{
    if (!_importManagedObjectContext) {

        // Setup MOC attached to parent mainUIMOC in private queue
        _importManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [_importManagedObjectContext setParentContext:[self mainUIManagedObjectContext]];
    }
    return _importManagedObjectContext;
}

这段代码非常简单。我仅使用 NSMainQueueConcurrencyType 中的 mainUIManagedObjectContext 复制上图。每次保存子上下文 importManagedObjectContext 时,都会触发一个通知,并且所有父上下文都会在其当前线程中执行保存操作。

我已经实现了一个带有 UITableViewNSFetchedResultsController 的测试 View Controller 。这是我的测试 View Controller 的 viewDidLoad 中的代码:

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Task"];
    [request setSortDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"insertDate" ascending:NO]]];
    self.fetchRequest = request;
    NSFetchedResultsController *frc =
    [[NSFetchedResultsController alloc]
     initWithFetchRequest:self.fetchRequest
     managedObjectContext:[AMBCoreData mainUIManagedObjectContext]
     sectionNameKeyPath:nil
     cacheName:nil];
    frc.delegate = self;

    [self setFetchedResultsController:frc];
    [self.fetchedResultsController performFetch:nil];
}

在这里,我将 mainUIManagedObjectContext 附加到 NSFetchedResultsController。稍后,在我的 viewDidAppear 中,我运行一个循环来插入一些任务实体:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [[AMBCoreData importManagedObjectContext] performBlock:^{
        for (int i = 0; i < 5000; i++) {
            Task *task = [NSEntityDescription insertNewObjectForEntityForName:@"Task" inManagedObjectContext:[AMBCoreData importManagedObjectContext]];
            task.title = [NSString stringWithFormat:@"Task %d", i];
            task.insertDate = [NSDate new];
        [[AMBCoreData importManagedObjectContext] save:nil];
    }];
}

问题是,我插入了 5000 个对象,当数据填充到 TableView 时,UI 卡住了。弗洛里安·库格勒 ran a test with this architecture插入 15.000 个对象并使用 instruments 他得到了主线程的使用(蓝色代表主线程,灰色代表任何其他线程):

Main thread CPU usage with three-layer context architecture

但这是我的 5000 个对象的主线程 CPU 使用情况,使用 iPhone 5 进行分析:

enter image description here

如您所见,我的主线程使用率远高于 Florian,而且我的 UI 卡住了几秒钟。我的问题是,我做错了什么吗?这是使用带有 NSFetchedResultsControllerUITableView 的三层 MOC 架构时的预期行为吗?我知道插入 5000 个对象不是大多数应用程序的通常行为,所以当我尝试使用 50 或 100 个对象时,卡住不存在或不明显,但主线程使用率很高(尽管我承认在这种情况下它可以由于其他原因,如唤醒应用程序)。

最佳答案

是的,这是意料之中的,因为主要 MOC 参与了其 child 的储蓄。当 UI 上下文的子级不进行大保存时,这很方便并且还可以,但如果这些保存较大,通常会成为性能问题。使用此模式时,您无法确定 UI 线程只完成最少的工作。

为了节省大量资金,我建议创建一个直接使用持久存储协调器配置的上下文。大保存发生后,您只需重新获取并可选择刷新 UI 上下文中的数据。有关详细信息,请参阅我的回答 here .

关于ios - CoreData 子上下文、NSFetchedResultsController 和主线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21476559/

相关文章:

objective-c - 使用 ARC 从导航 Controller 弹出时保留 View Controller

multithreading - 线程安全与重入

ios - 嵌入另一个 View 时,SwiftUI 动画不起作用

iOS蓝牙设备在输入错误密码后无法取消配对或忘记

ios - UIGraphicsBeginImageContext drawInRect不准确

objective-c - UIPageViewController 和景观

ios - Grand Central Dispatch EXC_BAD_ACCESS 异常

objective-c - 如何在 Cocoa/Objective-C 中使用应用程序委托(delegate)?

java - 图形 2d 对象的引用在孤立线程中不起作用

java - 为什么运行方法结束时的打印输出语句不打印出来